Finding text (multiple times) and highlighting - google-apps-script

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!

Related

Google Apps Script document body doesn't include content from batchUpdate API call

I'm trying to create a script that adds section breaks before any Heading 1 in a Google doc, and that part is working: Before, After
However, I would like to continue to do other things within the script as well, such as delete blank lines. I found out by printing out all the elements in the body of the document that the new elements from the batch update (section break and a blank line inserted with it) are not included. Using my function findType(), it prints "Paragraph" 3x rather than "Paragraph" 2x, "Unsupported", "Paragraph" 2x I cannot figure out why it does not see the new elements.
My code:
function insertSectionBreaks() {
let link = 'link to doc';
var doc = DocumentApp.openByUrl(link);
const documentId = doc.getId();
var body = doc.getBody();
// Define the search parameters.
var searchType = DocumentApp.ElementType.PARAGRAPH;
var searchHeading = DocumentApp.ParagraphHeading.HEADING1;
var searchResult = null;
var childIndex = null;
var docsObj = null;
var resource = {requests: []};
// Search until the paragraph is found.
while (searchResult = body.findElement(searchType, searchResult)) {
var paragraph = searchResult.getElement().asParagraph();
if (paragraph.getHeading() == searchHeading) {
//Here is where a heading is
childIndex = body.getChildIndex(paragraph);
docObj = Docs.Documents.get(documentId).body.content
resource.requests.push(
{insertSectionBreak: {
sectionType: "CONTINUOUS",
//location: {index: childIndex}
location: {index: docObj[childIndex+1].startIndex + resource.requests.length*2}//Multiply by two bc it adds a line for the section and a blank line on top
}}
);
}
}
Docs.Documents.batchUpdate(resource, documentId)
body = DocumentApp.openById(documentId);
for(var i = 0; i < body.getNumChildren(); i++){
findType(body.getChild(i));
}
}
function findType(element){
switch(element.getType()) {
case DocumentApp.ElementType.PARAGRAPH:
console.log("Paragraph");
return DocumentApp.ElementType.PARAGRAPH
break;
case DocumentApp.ElementType.UNSUPPORTED:
console.log("Unsupported");
return DocumentApp.ElementType.UNSUPPORTED
break;
default:
console.log("NONE");
return null;
}
}
I would really appreciate any help.

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

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+'")' );
}
}
}

replacing strings in a document and undo

In a mailMerge script I'm working on I use .replaceText() to replace fields with their corresponding values in a database.
The interface allows to test in the document to see if the result is looking as expected and I need to have a 'UNDO' function to get my fields in their original position so that I can use it with other values.(this script is bounded to a document in a side bar, see this post for illustration)
The script below does that pretty well by keeping in memory the field names an their replacement values.
The only detail that bothers me is that I had to define a special "empty" label for fields that have no values in the current test data to prevent losing their track in the document.
(I used a numbered identifier like °vide12°).
This is working perfectly but it's not ideal since the document in test mode is not exactly a representation of the final document because of these °videXX° that I use...
The question is : does anyone have a better idea or another approach to "localize" the replacement data when there is no data in a less visible way ? (I know this sound weird... that's why I explain the whole situation :-)
Considering the way Google Docs are build I thought that I could get the complete element structure and rebuild the doc from that info but I'm afraid it won't be possible since the smallest element is a paragraph and fields are mainly just single words...
Here is the relevant part of the code I use, I added a few comments to make it (hopefully) clear.
function valuesInDoc(e){ // this function replaces the fields with database values
var app = UiApp.getActiveApplication();
var listVal = UserProperties.getProperty('listSel').split(',');
var replacements = [];
var doc = DocumentApp.getActiveDocument();
var body = doc.getBody();
var find = body.findText('#ch');
if(find == null){var ui = DocumentApp.getUi() ; ui.alert("Aucun champ (#chX#) trouvé dans le document... Veuillez insérer des identifiants aux endroits souhaités");return app};
var curData = UserProperties.getProperty('selItem').split('|');
var Headers = [];
var OriHeaders = UserProperties.getProperty('Headers').split('|');
for(n=0;n<OriHeaders.length;++n){
Headers.push('#'+OriHeaders[n]+'#');
}
var fctSpe = 0 ;
for(var i in Headers){if(Headers[i].indexOf('SS')>-1){fctSpe = i}}
for(var n=0;n<listVal.length;++n){
var realIdx = Number(listVal[n]);
var newField = ChampSpecial(curData,realIdx,fctSpe);
if(newField!=''){replacements.push(newField+'∏'+'#ch'+(n+1)+'#')};
//Logger.log('value in '+n+'='+realIdx+' >> '+Headers[realIdx]+' = '+ChampSpecial(curData,realIdx,fctSpe))
app.getElementById('textField'+(n+1)).setHTML(ChampSpecial(curData,realIdx,fctSpe));
if(e.parameter.source!='dataSelection'){
body.replaceText('#ch'+(n+1)+'#',newField);
}
}
UserProperties.setProperty('replacements',replacements.join('|'));// memorize the replacement pattern
cloakOn();// hide hidden fields
return app;
}
function fieldsInDoc(e){ // this function does the reverse process and restores the field identifiers
cloakOff();// show hidden fields
var replacements = UserProperties.getProperty('replacements').split('|');
var doc = DocumentApp.getActiveDocument();
var body = doc.getBody();
for(var n=0;n<replacements.length;++n){
var field = replacements[n].split('∏')[1];
var testVal = replacements[n].split('∏')[0];
body.replaceText(testVal,field);
}
}
function ChampSpecial(curData,idx,ref){ // this function handles a special case for a specific field, the relevant part is right below, see comment
if(idx==-1){return''};
if(curData[idx-1]==''){return'°vide'+idx+'°'};// this is the "empty" identifier
if(idx<ref){return curData[idx]};
if(idx>ref){return curData[idx-1]}
var firstSpace = curData[idx-1].indexOf(' ');
var apos = curData[idx-1].indexOf("'");
//Logger.log('firstSpace='+firstSpace+' apos='+apos)
if(firstSpace<4&&firstSpace>-1){return curData[idx-1].substring(firstSpace+1)};
if(apos<3&&apos>-1){return curData[idx-1].substring(apos+1)};
return curData[idx-1];
}
EDIT : thanks to Mogsdad's brilliant answer I wrote these 2 functions to hide/show the unused fields. Sinc in my case I use °XX° (XX=2 digit number) to keep track of the unused fields I had to modify his code to look for this particular string and used 2 loops to get all the fields.
I call these function from the menu AND from the two other functions that handle the replacement (I updated the code above as well)
It might appear a waste of time since I iterate more that 100 times but the result is instantaneous... so why bother ?
here is the code in case it gives someone an idea.
function cloakOn() {
var doc = DocumentApp.getActiveDocument();
var body = doc.getBody();
var found = [];
for(var n=1;n<23;++n){
for(var f=0;f<5;++f){
if(f==0){found[f] = body.findText('°'+Utilities.formatString("%02d",n)+'°')}else{found[f] = body.findText('°'+Utilities.formatString("%02d",n)+'°',found[f-1])}
if(found[f]!=null){
var elemTxt = found[f].getElement().asText();
elemTxt.setFontSize(found[f].getStartOffset(), found[f].getEndOffsetInclusive(),0)
var background = elemTxt.getBackgroundColor(found[f].getStartOffset()) || "#ffffff";
elemTxt.setForegroundColor(found[f].getStartOffset(), found[f].getEndOffsetInclusive(), background);
}
}
}
}
function cloakOff() {
var doc = DocumentApp.getActiveDocument();
var body = doc.getBody();
var found = [];
for(var n=1;n<23;++n){
for(var f=0;f<5;++f){
if(f==0){found[f] = body.findText('°'+Utilities.formatString("%02d",n)+'°')}else{found[f] = body.findText('°'+Utilities.formatString("%02d",n)+'°',found[f-1])}
if(found[f]!=null){
var elemTxt = found[f].getElement().asText();
var size = elemTxt.getParent().getFontSize();
elemTxt.setFontSize(found[f].getStartOffset(), found[f].getEndOffsetInclusive(),size)
var background = elemTxt.getBackgroundColor(found[f].getStartOffset()) || "#000000";
elemTxt.setForegroundColor(found[f].getStartOffset(), found[f].getEndOffsetInclusive(), background);
}
}
}
}
Serge, I've been working on the very same problem! I've got a partial workaround to share, and some ideas to take it further.
There is no way to embed hidden text in Google Docs, as eloquently stated by Gill on the old forum. If there was, your mailmerge would be trivial!
How about making your tags or "cookies" (almost) invisible, though? Below is a scriplet that adds a "cloaking" function to a document. It has extras as well; it queries the user for text to cloak, then searches for all instances of that text and cloaks them. The idea I settled on was to make the text as small as possible (fontsize 0) and to match the foreground color to the background color.
// in menu: .addItem('Text Cloaking', 'cloakOn')
/**
* Find all matches of target text in current document, and cloak them.
* At this time, that consists of making the text tiny, but still visible.
* This is an experiment - my hope was to find a way to implement something
* like document variables, placeholders that would not be forgotten, so
* that values could be changed, or even dynamic.
*
* #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 cloakOn(target) {
// If no search parameter was provided, ask for one
if (arguments.length == 0) {
var ui = DocumentApp.getUi();
var result = ui.prompt('Text Cloaking',
'Enter text to cloak:', ui.ButtonSet.OK_CANCEL);
// Exit if user hit Cancel.
if (result.getSelectedButton() !== ui.Button.OK) return;
// else
target = result.getResponseText();
}
var doc = DocumentApp.getActiveDocument();
var bodyElement = doc.getBody();
var searchResult = bodyElement.findText(target);
while (searchResult !== null) {
var thisElement = searchResult.getElement();
var thisElementText = thisElement.asText();
//Logger.log(url);
thisElementText.setFontSize(searchResult.getStartOffset(), searchResult.getEndOffsetInclusive(),0);
var background = thisElementText.getBackgroundColor(searchResult.getStartOffset()) || "#ffffff";
thisElementText.setForegroundColor(searchResult.getStartOffset(), searchResult.getEndOffsetInclusive(),
background);
// search for next match
searchResult = bodyElement.findText(target, searchResult);
}
}
To make use of this in the text-replacement operation, the replacement text would carry a cloaked tag (as you're doing). I think you'd want to make your tags as short as possible, so that the white space they occupy in the final document is very small - I was playing with using a series of unicode characters as digits, to give a large range of 2-digit 'numbers' that would be unlikely to show up in any other context.

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.