Replace text with hyperlink using Google apps script - 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);
}
}

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

Related

Cannot update text in paragraph of NamedRange

I have a radio button with a few options in my google Doc. When the user selects an option, it adds a section with a heading and also paragraph. It should add the section only if the section isn't available and append the 'updated' text to the paragraph.
If the section is already available, it should only append the updated text at the end of the text.
Issue: Actually the behavior is pretty strange:
For instance, if I select the section named 'Diagnoses' it will be created correctly.
Diagnoses
updated text, new range? true
If I select the section named 'Anamnese'. the section will be also created correctly:
Diagnoses
updated text, new range? false
Anamnese
updated text, new range? true
If I switch back to 'Diagnoses' it looks like this:
Diagnose
updated text, new range? true
Anamneseupdated text, new range? false
updated text, new range? trueupdated text, new range? false
function manageSection(selectedSection) {
section = selectedSection;
var range = doc.getNamedRanges().find(r => r.getName() == section);
if (!range) {
Logger.log('No named range. Installing a named range');
setNamedRange(section)
} else {
Logger.log('Named range found.');
getNamedRange(section)
}
}
// No namedRange found - Set new named range with name section.
function setNamedRange(section) {
goToLastLine();
// Append a section header
var s = body.appendParagraph(section);
s.setHeading(DocumentApp.ParagraphHeading.HEADING4);
// Append paragraph to section
var insert = body.appendParagraph('');
var rangeBuilder = doc.newRange();
rangeBuilder.addElement(insert);
var savedInsert = rangeBuilder.build()
var namedRange = doc.addNamedRange(section, savedInsert);
var namedRangeId = namedRange.getId();
// select the namedRange
doc.setSelection(doc.getNamedRangeById(namedRangeId).getRange());
var newRange = true;
changeTextByNamedRange(section, newRange)
}
// Existing namedRange found.
function getNamedRange(section) {
Logger.log('getNamedRange() started')
var newRange = false;
changeTextByNamedRange(section, newRange)
}
// Update named range.
function changeTextByNamedRange(section, newRange) {
docUi.alert('section:' + section);
var range = doc.getNamedRanges().find(r => r.getName() == section);
// test
var updateText = "updated text, new range? " + newRange;
//
range.getRange().getRangeElements().forEach(e => e.getElement().asText().appendText(updateText));
}
function goToLastLine(){
const kids = body.getNumChildren()
const lastKid = body.getChild(kids - 1)
let last = 0
try {
const lastPar = body.getChild(kids - 1).asParagraph()
last = doc.newPosition(lastPar.getChild(0), lastPar.getText().length)
} catch (e) {
last = doc.newPosition(body.getChild(kids - 1), 0)
} finally {
doc.setCursor(last)
}
}
I have a workaround working and feel free to modify if it still is within your goal.
I modified setNamedRange and changeTextByNamedRange functions. Here are their modifications:
setNamedRange:
function setNamedRange(section) {
// Append a section header
goToLastLine();
var s = body.appendParagraph(section);
s.setHeading(DocumentApp.ParagraphHeading.HEADING4);
// Append paragraph to section
// for some reason, it fails to include the 1st paragraph when it is blank
// you can add a space, or any character just to include the 1st one properly
var insert = body.appendParagraph("\t");
var rangeBuilder = doc.newRange();
rangeBuilder.addElement(insert);
var savedInsert = rangeBuilder.build();
var namedRange = doc.addNamedRange(section, savedInsert);
var namedRangeId = namedRange.getId();
// select the namedRange
doc.setSelection(doc.getNamedRangeById(namedRangeId).getRange());
changeTextByNamedRange(section);
}
changeTextByNamedRange:
function changeTextByNamedRange(section) {
// docUi.alert('section:' + section);
var range = doc.getNamedRanges().find(r => r.getName() == section);
// get list of section names
var sectionNames = doc.getNamedRanges().map(r => r.getName());
var updateText = "Newer Text is appended for this section " + section + ". ";
// this section contains the succeeding sections as well
// so you have to end the loop when you encounter another section
// use some instead of forEach to be able to break the loop.
range.getRange().getRangeElements().some(e => {
var element = e.getElement();
// do anything you want. Note that this will loop to each element of that range.
// if section has multiple elements, it will append to each element.
// since we are only appending text to a single paragraph element, this currently works
element.asText().appendText(updateText)
// end loop when you encounter a text that is one of the section names
return !sectionNames.includes(element.asText())
});
}
Output:
After multiple runs on different sections, modifying the text to be appended, added some manual text then running manageSection again. This is the result.

Adding Incrementing Citation ID

I've tried searching the issue and came up with nothing, so I'm looking for help.
What I'm trying to do is add incrementing IDs to individual citations by:
Searching a Google Doc for a specific combination of characters that signal the end of the citation (checking to see if there are any in the document at all).
If the characters are there, I'd like to find the first one, then place a '1' in between the two characters. Then the second one should have a '2' between it, the third should have a '3', and so on and so forth ensuring that all of the sets of characters have been replaced with unique number IDs
What is going wrong currently:
Due to the elemental structure of the Google Doc, my script is replacing ALL instances within a paragraph with the same ID number, which can be 1 instance or it can be 20. When it moves to the next paragraph, every found instance of the character combination is getting the incremented ID.
I need a genius' help.
Here's the code base I've found that I've been struggling to modify:
var doc = DocumentApp.getActiveDocument();
var body = doc.getBody();
var foundElement = body.findText("]]");
var i = 1;
while (foundElement != null) {
// Get the text object from the element
var foundText = foundElement.getElement().asText();
// Where in the Element is the found text?
var start = foundElement.getStartOffset();
var end = foundElement.getEndOffsetInclusive();
// Change the text
foundText.replaceText("]]","]"+i+"]");
// Find the next match
foundElement = body.findText("]]", foundElement);
i++
}
Issue
replaceText() will replace all the occurence in the element.
Solution
To avoid this, you should use the start and end to delete and insert text.
Please see my code below. This worked on my end.
var doc = DocumentApp.getActiveDocument();
var body = doc.getBody();
var foundElement = body.findText("]]");
var i = 1;
while (foundElement != null) {
// Get the text object from the element
var foundText = foundElement.getElement().asText();
// Where in the Element is the found text?
var start = foundElement.getStartOffset();
var end = foundElement.getEndOffsetInclusive();
// Change the text
foundText.deleteText(start, end)
foundText.insertText(start, "]"+i+"]")
// Find the next match
foundElement = body.findText("]]", foundElement);
i++
}
As you can see, I just modified the line where you replaceText() and transform it to a combination of deleteText() and insertText() methods.
Try this:
function myfunction() {
var doc = DocumentApp.getActiveDocument();
var body = doc.getBody();
var foundElement = body.findText("]]");
var i = 1;
while (foundElement != null) {
var foundText = foundElement.getElement().asText();
var start = foundElement.getStartOffset();
var end = foundElement.getEndOffsetInclusive();
foundText.replaceText("]]", "]" + i++ + "]");
foundElement = body.findText("]]", end);//Modifed this and the end point of the last search. And the i++ will use the i as it currently is and then increment it after the current operation.
}
}
Each next search needs to start searching after the location of the preceeding element. range element getEndOffsetInclusive()

How to add a hyperlink in a Google Docs using a Google Script

I have always used the insertText() function, but now I want to write a link in my google docs. The ideal would be to be able to write in HTML, but I don't know how.. it seems that it is not possible with the insertText() function.
How can I do that ?
You should be able to use setFormula and the Hyperlink formula like so:
var value = '=HYPERLINK("www.google.com", "Google")';
SpreadsheetApp.getActiveSpreadsheet()
.getSheetByName("Sheet1")
.getRange("A1")
.setFormula(value);
Edit:
Looks like I misread the question. Try this instead:
DocumentApp.getActiveDocument()
.getBody()
.editAsText()
.insertText(0, "link text")
.setLinkUrl("www.google.com");
Edit 2: Looks like .setLinkUrl() is effecting the whole body, not the text inserted. If you put the link text into a variable and use the length of the variable to mark the link area, it should work. Try this instead:
function insertLink() {
var text = "link text\n";
var url = "www.google.com";
DocumentApp.getActiveDocument()
.getBody()
.editAsText()
.insertText(0, text)
.setLinkUrl(0, text.length, url);
}
To add a hyperlink in a document use Body.appendParagraph with setLinkUrl, then merge.
let doc = DocumentApp.create("My Document");
let body = doc.getBody();
body.appendParagraph("Please click ");
let link = body.appendParagraph("here").setLinkUrl("http://www.google.com");
link.merge();
let closing = body.appendParagraph(".");
closing.merge();
The code above will create a document with text that looks like:
Please click here.
You can also use the function below to make all the links in your document clickable.
function makeLinksClickable(document) {
const URL_PATTERN="http[^\s]+"
const URL_PATTER_LENGTH_CORECTION = "http ".length
const body = document.getBody()
var foundElement = body.findText(URL_PATTERN);
while (foundElement != null) {
// Get the text object from the element
var foundText = foundElement.getElement().asText();
// Where in the element is the found text?
const start = foundElement.getStartOffset();
const end = foundElement.getEndOffsetInclusive() - URL_PATTER_LENGTH_CORECTION;
const url = foundText.getText().substring(start,end)
//make url clickable
foundText.setLinkUrl(start, end, url)
// Find the next match
foundElement = body.findText(URL_PATTERN, foundElement);
}
}
If you are looking to find a string with a hyperlink, the following code will work.
function insertLink(){
const body = DocumentApp.getActiveDocument().getBody()
const text = body.findText('{{googleLink}}').getElement().asText()
text.setText('Link to google')
text.setLinkUrl('www.google.com')
}
I am using this script, this is working Calomun 1 Row > 2.
function InsertLink(e)
{
var actSht = e.source.getActiveSheet();
if (actSht.getName() == ['SheetName']){
var activeCell = actSht.getActiveCell(); //Detec the ActiveCell
//var activeCell = event.range;
var activeCellValue = e.value;
var column = activeCell.getColumn();
var colNums = [1]; //Columns, whose edit is considered
if(colNums.indexOf(column) == -1) return; //If column other than considered then return
var row = activeCell.getRow();
if(row < 2) return; //If header row then return
var length = String(activeCellValue).length;
if (!e.value)
{
activeCell.setValue()
}
else if(length > 4)
{
activeCell.setValue('=HYPERLINK' + '("http://otrs/otrs/index.pl?Action=AgentTicketZoom;TicketNumber='+activeCellValue+'";"'+activeCellValue+'")' );
}
}
}

Can I color certain words in Google Document using Google Apps Script?

I'm trying to highlight certain words in my Google Document. I know I can replace text using document.replace, but it only replaces string itself, not formatting. Is there a way to replace string with colored string using Google Apps Script?
With the introduction of document-bound scripts, it's now possible to make a text highlighting function that's invoked from a custom menu.
Surely THIS is the best answer now! 8^)
This script was modified from the one in this answer, and may be called from the UI (with no parameters) or a script.
/**
* Find all matches of target text in current document, and highlight them.
*
* #param {String} target (Optional) The text or regex to search for.
* See Body.findText() for details.
* #param {String} background (Optional) The desired highlight color.
* A default orange is provided.
*/
function highlightText(target,background) {
// If no search parameter was provided, ask for one
if (arguments.length == 0) {
var ui = DocumentApp.getUi();
var result = ui.prompt('Text Highlighter',
'Enter text to highlight:', ui.ButtonSet.OK_CANCEL);
// Exit if user hit Cancel.
if (result.getSelectedButton() !== ui.Button.OK) return;
// else
target = result.getResponseText();
}
var background = background || '#F3E2A9'; // default color is light orangish.
var doc = DocumentApp.getActiveDocument();
var bodyElement = DocumentApp.getActiveDocument().getBody();
var searchResult = bodyElement.findText(target);
while (searchResult !== null) {
var thisElement = searchResult.getElement();
var thisElementText = thisElement.asText();
//Logger.log(url);
thisElementText.setBackgroundColor(searchResult.getStartOffset(), searchResult.getEndOffsetInclusive(),background);
// search for next match
searchResult = bodyElement.findText(target, searchResult);
}
}
/**
* Create custom menu when document is opened.
*/
function onOpen() {
DocumentApp.getUi().createMenu('Custom')
.addItem('Text Highlighter', 'highlightText')
.addToUi();
}
This is a better solution:
function highlightTextTwo() {
var doc = DocumentApp.openById('<your document id');
var textToHighlight = 'dusty death';
var highlightStyle = {};
highlightStyle[DocumentApp.Attribute.FOREGROUND_COLOR] = '#FF0000';
var paras = doc.getParagraphs();
var textLocation = {};
var i;
for (i=0; i<paras.length; ++i) {
textLocation = paras[i].findText(textToHighlight);
if (textLocation != null && textLocation.getStartOffset() != -1) {
textLocation.getElement().setAttributes(textLocation.getStartOffset(),textLocation.getEndOffsetInclusive(), highlightStyle);
}
}
}
Previous Answer:
The key is to being able to reference just the words you want to color.
My solution is to:
Get the text of the paragraph that contains the words you wish to color, remove the original paragraph, then add each part of the text back. As you add each part back the appendText returns a reference to just the text added, you then can specify its color with setForegroundColor():
function highlightText() {
var doc = DocumentApp.openById('<your document id>');
var textToHighlight = 'dusty death';
var textLength = textToHighlight.length;
var paras = doc.getParagraphs();
var paraText = '';
var start;
for (var i=0; i<paras.length; ++i) {
paraText = paras[i].getText();
start = paraText.indexOf(textToHighlight);
if (start >= 0) {
var preText = paraText.substr(0, start);
var text = paraText.substr(start, textLength);
var postText = paraText.substr(start + textLength, paraText.length);
doc.removeChild(paras[i]);
var newPara = doc.insertParagraph(i, preText);
newPara.appendText(text).setForegroundColor('#FF0000');
newPara.appendText(postText).setForegroundColor('#000000');
}
}
}
I think it's possible with the method setBackgroundColor of class Text in DocumentApp : https://developers.google.com/apps-script/class_text#setBackgroundColor
You'll have to retrieve your words as Text elements. In order to do that you can use the find method of your object Document, then to iterate over the search results and use getElement. Finally, to convert your Element object into a Text object, you can use asText().
Hope it'll work ! ;)
This is available as a Google docs add-on named Multi-instance Text Highlighting. Hints: At first it didn't seem to work, but I closed my doc and re-opened it, and then it worked. Then it didn't seem to work now and then, but I found out that special characters in your text string can break it; I think I had a + in my string and it just didn't do anything. But without special characters, it works great. Really helped me out.

Finding text (multiple times) and highlighting

I would like to find all the instances of a word in a Google doc and highlight them (or comment - anything so it stands out). I have created the following function, but it only finds the first appearance of the word ("the" in this case). Any ideas on how to find all instances of the word would be appreciated!
function findWordsAndHighlight() {
var doc = DocumentApp.openById(Id);
var text = doc.editAsText();
//find word "the"
var result = text.findText("the");
//change background color to yellow
result.getElement().asText().setBackgroundColor(result.getStartOffset(), result.getEndOffsetInclusive(), "#FFFF00");
};
I know this is an oldie, but here's how I add effects to text in Google Script. The example below is specifically for adding highlighting to all occurrences of a particular string in a document.
function highlightText(findMe) {
var body = DocumentApp.getActiveDocument().getBody();
var foundElement = body.findText(findMe);
while (foundElement != null) {
// Get the text object from the element
var foundText = foundElement.getElement().asText();
// Where in the Element is the found text?
var start = foundElement.getStartOffset();
var end = foundElement.getEndOffsetInclusive();
// Change the background color to yellow
foundText.setBackgroundColor(start, end, "#FCFC00");
// Find the next match
foundElement = body.findText(findMe, foundElement);
}
}
Ok so, chaining your codes it could finish like this :
function findWordsAndHighlight() {
var doc = DocumentApp.openById("DocID");
var text = doc.editAsText();
var search = "searchTerm";
var index = -1;
var color ="#2577ba";
var textLength = search.length-1;
while(true)
{
index = text.getText().indexOf(search,index+1);
if(index == -1)
break;
else text.setForegroundColor(index, index+textLength,color );
}
};
I still have a doubt.
This code works nice, but why I have to use search.length-1?
With the introduction of document-bound scripts, it's now possible to make a text highlighting function that's invoked from a custom menu.
This script was modified from the one in this answer, and may be called from the UI (with no parameters) or a script.
/**
* Find all matches of target text in current document, and highlight them.
*
* #param {String} target (Optional) The text or regex to search for.
* See Body.findText() for details.
* #param {String} background (Optional) The desired highlight color.
* A default orange is provided.
*/
function highlightText(target,background) {
// If no search parameter was provided, ask for one
if (arguments.length == 0) {
var ui = DocumentApp.getUi();
var result = ui.prompt('Text Highlighter',
'Enter text to highlight:', ui.ButtonSet.OK_CANCEL);
// Exit if user hit Cancel.
if (result.getSelectedButton() !== ui.Button.OK) return;
// else
target = result.getResponseText();
}
var background = background || '#F3E2A9'; // default color is light orangish.
var doc = DocumentApp.getActiveDocument();
var bodyElement = DocumentApp.getActiveDocument().getBody();
var searchResult = bodyElement.findText(target);
while (searchResult !== null) {
var thisElement = searchResult.getElement();
var thisElementText = thisElement.asText();
//Logger.log(url);
thisElementText.setBackgroundColor(searchResult.getStartOffset(), searchResult.getEndOffsetInclusive(),background);
// search for next match
searchResult = bodyElement.findText(target, searchResult);
}
}
/**
* Create custom menu when document is opened.
*/
function onOpen() {
DocumentApp.getUi().createMenu('Custom')
.addItem('Text Highlighter', 'highlightText')
.addToUi();
}
Well, simple javascript is enough,
var search = searchtext;
var index = -1;
while(true)
{
index = text.indexOf(search,index+1);
if(index == -1)
break;
else
/** do the required operation **/
}
Hope this helps!