Formatting in replaceText() - google-apps-script

I've got doc.getBody().replaceText(oldregex,newstring) working fine in a Google Document script at the minute, and was hoping to set some bold/italic on newstring. This looks harder than I thought it would be. Has anyone found a tidy way to do this?
I'm currently thinking I'll need to...
Build newtext as a range with rangeBuilder
Find oldtext and select it as a range (somehow...)
Clear the oldtext range and insert the newtext range at the find location
This seems like a lot of work for something that would be trivial with HTML-like tags. I'm definitely missing something. Would really appreciate any suggestions.

Since replaceText only changes the plain text content, leaving formatting in place, the goal can be achieved by applying formatting before the replacement. First, findText goes through the text and sets bold to every match; then replaceText performs the replacement.
There are two cases to consider: only a part of text in an element is matched (which is typical) and entire element is matched. The property isPartial of RangeElement class distinguishes between these.
function replaceWithBold(pattern, newString) {
var body = DocumentApp.getActiveDocument().getBody();
var found = body.findText(pattern);
while (found) {
var elem = found.getElement();
if (found.isPartial()) {
var start = found.getStartOffset();
var end = found.getEndOffsetInclusive();
elem.setBold(start, end, true);
}
else {
elem.setBold(true);
}
found = body.findText(pattern, newString);
}
body.replaceText(pattern, newString);
}
This seems like a lot of work for something that would be trivial
This is both correct and typical for working with Google Documents using Apps Script.

Related

Find and format all words that start with a set string in Google Docs

I made a function in Google Apps Script that searches for all words in a Google Docs and changes their colors to a desired color. It takes as inputs: the doc ID, the color desired and the word to look for.
However, what I really need is a function that finds all the words that start with a particular string. For example, "change all words that start with # to blue". I tried messing with findText() but had no luck. Any ideas on how to fix the function below to do what I need? Thanks!
Currently, my function looks like this:
function colorTheWords(findMe,color,documentID) {
//color input must be formatted in CSS notation like '#ffffff'
//documentID must be formated as text in between ''
//findMe word must be formatted as ''
//open doc
var document = DocumentApp.openById(documentID);
var body = document.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 current color to the desired color
foundText.setForegroundColor(start, end, color);
// Find the next match
foundElement = body.findText(findMe, foundElement);
}
}
You can use regular expressions findText, which will allow you to do this easily. There is an answer to a similar question here:
Regex to check whether string starts with, ignoring case differences
I always use this site to help me to test my regular expressions before adding them to the code. Paste the contents of your document in and then fiddle with your regex until you just select what you need.
https://regexr.com/
The main issue you are encountering is that findText does not use normal regular expressions but a flavour called re2. This has some slight variations and restrictions. If you want to find all words that start with a specific string or character, this is the expression you should be using:
#([^\s]+)

Google App Script replaceText to replace only first occurrence of matched string

I would like to use google appscript to replace text on my google doc to convert it to PDF. But the problem is the function replaceText(textToReplace, newText); just remove every occurrence of the matched text. I just want to remove only the first occurrence. How to do that?
The replaceText method can be limited in scope to an element, by calling it on that element. But that does not help if the first paragraph where the text is found contains multiple instances of it: they are all going to be replaced.
Instead, use findText to find the first match, and then call deleteText and insertText to execute replacement.
// replaces the first occurrence of old
function replaceFirst(old, replacement) {
var body = DocumentApp.getActiveDocument().getBody();
var found = body.findText(old);
if (found) {
var start = found.getStartOffset();
var end = found.getEndOffsetInclusive();
var text = found.getElement().asText();
text.deleteText(start, end);
text.insertText(start, replacement);
}
}
If you think this ought to be easier, you are not alone.

How to copy ListItems from one Google Document to another while preserving numbering?

The accepted answer to How to copy content and formatting between Google Docs? indicates that we have to add conditional code just to copy elements. But I cannot get it to work for ListItem types, because the target document shows the list items without the original numbering.
var source_doc = DocumentApp.getActiveDocument();
var selection = source_doc.getSelection();
if (!selection) {
var ui = DocumentApp.getUi();
ui.alert('Please make a selection first.');
return;
}
var target_doc = DocumentApp.create('CopyOf'+DocumentApp.getActiveDocument().getName());
var target_body = target_doc.getBody();
var elements = selection.getRangeElements();
for (var i = 1; i < elements.length; i++) {
var source_element = elements[i].getElement();
var copy_element = source_element.copy();
if (copy_element.getType() == DocumentApp.ElementType.PARAGRAPH) {
target_body.appendParagraph(copy_element);
} else if (copy_element.getType() == DocumentApp.ElementType.LIST_ITEM) {
// This does not keep the numbering on the list item. Why?
target_body.appendListItem(copy_element);
// And playing games with setListId doesn't work either:
// copy_element.setListId(source_element);
// target_body.appendListItem(copy_element);
}
// TODO: Handle the other elements here.
}
The source document displays like this:
Target document renders like this:
How do I preserve ListItem formatting?
This seems much much harder than it should be: What I really want is to copy the users selection verbatim into a new document preserving all formatting, and from a google script.
It would seem that this could be done at a higher level. I can manually copy and paste and preserve the formatting, just not from the script.
I'm guessing that the cause of this is that there's a problem with using a Selection. Reading from a document directly seems to work fine.
Try appending the ListItem as text as a workaround.
target_body.appendListItem(copy_element.getText());
This will only copy the text though, not the formatting. You can also try to implement it by making a new list instead of copying the element directly. Here's a sample SO that might help.
I was having a similar problem (but not using a selection). It was being copied as a list but without any actual bullets. I just re-set the bullets manually like this:
target_body.appendListItem(copy_element).setGlyphType(DocumentApp.GlyphType.NUMBER)

Function for word count in Google Docs Apps Script

Is there a method in Google Apps Scrips that returns the word count from a Google Document?
Lets say I'm writing a report that have a particular limit on word count. It's quite precise and it states exactly 1.8k - 2k words (yes and it's not just a single case, but many...)
In Microsoft Office Word there was a handy status bar at the bottom of the page which automatically updated the word count for me, so I tried to make one using Google Apps Scrips.
Writing a function that rips out whole text out from a current document and then calculates words again and again several times in a minute feels like a nonsense to me. It's completely inefficient and it makes CPU run for nothing but I couldn't find that function for the word count in Docs Reference.
Ctr+Shift+C opens a pop-up that contains it, which means that a function that returns total word count of a Google Document definitely exists...
But I can't find it!
Sigh... I spent few hours digging through Google, but I simply cannot find it, please help!
Wrote a little snippet that might help.
function myFunction() {
var space = " ";
var text = DocumentApp.getActiveDocument().getBody().getText();
var words = text.replace(/\s+/g, space).split(space);
Logger.log(words.length);
}
I understand the the request is for a built in function, which I looked for as well, but couldn't find anywhere in the documentation. I had to use polling.
I started with a script like Amit's, but found that I was never matching Google's word count. This is what I had to do to get it work. I know this can't be efficient, but it now matches google docs count most of the time. What I had to do was clean/rebuild the string first, then count it.
function countWords() {
var s = DocumentApp.getActiveDocument().getBody().getText();
//this function kept returning "1" when the doc was blank
//so this is how I stopped having it return 1.
if (s.length === 0)
return 0;
//A simple \n replacement didn't work, neither did \s not sure why
s = s.replace(/\r\n|\r|\n/g, " ");
//In cases where you have "...last word.First word..."
//it doesn't count the two words around the period.
//so I replace all punctuation with a space
var punctuationless = s.replace(/[.,\/#!$%\^&\*;:{}=\-_`~()"?“”]/g," ");
//Finally, trim it down to single spaces (not sure this even matters)
var finalString = punctuationless.replace(/\s{2,}/g," ");
//Actually count it
var count = finalString.trim().split(/\s+/).length;
return count;
}
I think this function probably covers most cases for word count with English characters. If I overlooked something, please comment.
function testTheFunction(){
var myDoc = DocumentApp.openByUrl('https://docs.google.com/document/d/?????/edit');
Logger.log(countWordsInDocument(myDoc));
}
function countWordsInDocument(theDoc){
var theText = theDoc.getBody().getText();
var theRegex = new RegExp("[A-Za-z]") // or include other ranges for other languages or numbers
var wordStarted = false;
var theCount = 0;
for(var i=0;i<theText.length;i++){
var theLetter = theText.slice(i,i+1);
if(theRegex.test(theLetter)){
if(!wordStarted){
wordStarted=true;
theCount++;
}
}else if(wordStarted){
wordStarted=false;
}
}
return theCount;
}

Google Docs: sending email with variable content from the spreadsheet

i want to send emails that contains text and variable values from the spreadsheet.
For that i ran the sendemails-script which works well. But at the example the cell that was declared as "var message" didnt contain variable values but only text.
Is it possible to add "references to values" to this text?
Something like:
Hello,
last week you bought (value of B3) apples and (value of B4) cherries.
I think in Excel you can split text and formular units in one row, is there something like that in GoogleDocs?
Thanks for your help in Advance, i hope you could understand what i mean :)
Marten
Amit's suggestion is a good idea although it might be a bit complex if you want to keep the whole thing simple and/or adapt it to your needs... It's never easy to analyse a script that is the result of a lot of work... from someone else.
So I wrote a very simple script that does exactly what you ask, nothing more (and nothing less I hope)
It uses 3 columns and the base message has 3 placeholders but you can of course expand it the way you want.
It will be easy to customize it to your use case. I added many log in the code so you see exactly what is going on.
function simpleMerge(){
var sh = SpreadsheetApp.getActiveSheet();
var data = sh.getDataRange().getValues();
var headers = data.shift();
for(var n=0 ; n<data.length ; n++){
var baseMessage = "Hello #0#, \n\nlast week you bought #1# apples and #2# cherries.";
for(var c=0 ; c<data[n].length ; c++){
var placeHolder = '#'+c+'#';
var value = data[n][c];
Logger.log('replacing '+placeHolder+' with '+value);
baseMessage = baseMessage.replace(placeHolder,value);
}
Logger.log('message '+n+' = '+baseMessage);
//send message to who you want
}
}
Install the ultradox or mailchimp add-on or many others that do this with more flexibility.