Apps-Script: Element equality? - google-apps-script

Since this is always false:
doc.getBody().getParagraphs()[0] == doc.getBody().getParagraphs()[0]
How do you test element equality in Apps-Script?

I'm not entirely sure if you are comparing the contents or the position. Let's assume you can compare the contents with getAsText().
To compare the position, it's fairly easy to create an element index (the path at which an element appears in a document).
/**
* create a path in document serial for an element
* #param {Document.Element} element an element
* #param {string} [path=''] the path so far
* #return {string} the path
*/
function pathInDocument (element,path) {
path = path || "" ;
var parent = element.getParent();
if (parent) {
path = pathInDocument( parent , Utilities.formatString ( '%04d.%s', parent.getChildIndex(element),path ));
}
return path;
};
which can be called like this
var path = pathInDocument(element);
and will return something like
0000.0001.0004....etc
If the paths of two elements are the same, they appear in the same position in the document and are therefore the same element.
For an example of using this (in this case to sort bookmarks) see https://ramblings.mcpher.com/google-docs/sorting-bookmarks-in-a-document/

Eventually I came up with a solution for comparing elements.
first of all let me point that this code works and returns true:
var paragraphs = doc.getBody().getParagraphs();
Logger.log(paragraphs[0] == paragraphs[0]);
that is because you are comparing the same element from an array. The way you did in the question, you had two different arrays of paragraphs.
However there are situations when you can not do that, because you may not be comparing paragraphs, or you don't even know what elements you have.
What I do is create a path to the elements all the way up to the body section of the Document. If the paths are equal, you have the same elements.
function bodyPath(el, path) {
path = path? path: [];
var parent = el.getParent();
var index = parent.getChildIndex(el);
path.push(index);
var parentType = parent.getType();
if (parentType !== DocumentApp.ElementType.BODY_SECTION) {
path = bodyPath(parent, path);
} else {
return path;
};
return path;
};
function isSameElement(element1, element2) {
var path1 = bodyPath(element1);
var path2 = bodyPath(element2);
if (path1.length == path2.length) {
for (var i=0; i<path1.length; i++) {
if (path1[i] !== path2[i]) {
return false;
};
};
} else {
return false;
};
return true;
};
This method has proved itself quite fast. Any additions are welcome!

I wrote a recursive solution to avoid string comparison and short-circuit the path walk. Note that you can always convert to loops if you're not happy with the stack dependency of recursion.
function isSameElement(elem1, elem2) {
if (!elem1 && !elem2) return true;
if (!elem1 || !elem2) return false;
var p1=elem1.getParent();
var p2=elem2.getParent();
if (!p1 && !p2) {
return true;
} else if (!p1 || !p2) {
return false;
} else if (p1.getChildIndex(elem1)==p2.getChildIndex(elem2)){
return isSameElement(p1,p2);
} else {
return false;
}
}

I tried it and its always false, for some reason the method returns different objects.
In this case you are comparing the objects and not the content of the objects which indeed are different. You could get the content of the object with .getText(), then this comparison would return true.

Related

finding Text with specific format and delete it

I have a big google doc file with over 100 pages(with tables etc) and there is some reference text in that document in multiple locations reference texts are highlighted with the color "grey", I want to have a function that can find those colors/style in the table or paragraph and delete it. So Step 1 is finding it, and then deleting(removing those texts from the document) it in one go.
How we did it in MS Word is, we created custom styles and assign those styles to those "Remarks Text"(in grey) and in VBA we look for text matching the style name, and if it returns true than we delete those texts. As much i know about doc, there is no option to create custom styles.
Here is the code I am trying:-
function removeText()
{
var doc = DocumentApp.getActiveDocument()
var body = doc.getBody()
body.getParagraphs().map(r=> {
if(r.getAttributes().BACKGROUND_COLOR === "#cccccc")
{
//Don't know what to do next, body.removeChild(r.getChild()) not working
}
})
}
Can you guide me on how I can achieve this effectively please.
Thanks
Try this
body.getParagraphs().forEach( r => {
if( r.getAttributes().BACKGROUND_COLOR === "#cccccc" ) {
r.removeFromParent();
}
}
Reference
Paragraph.removeFromParent()
Google Apps Script hasn't a method to find text based on their style attributes, instead we need to get each part and in order to be able to get their attributes. The following example, if the format is applied to the whole paragraph, it is deleted, if not, it uses the regular expression for finding any single character ..
function removeHighlightedText() {
// In case that we want to remove the hightlighting instead of deleting the content
const style = {};
style[DocumentApp.Attribute.BACKGROUND_COLOR] = null;
const backgroundColor = '#cccccc';
const doc = DocumentApp.getActiveDocument();
const searchPattern = '.';
let rangeElement = null;
const rangeElements = [];
doc.getParagraphs().forEach(paragraph => {
if (paragraph.getAttributes().BACKGROUND_COLOR === backgroundColor) {
paragraph.removeFromParent();
// Remove highlighting
// paragraph.setAttributes(style);
} else {
// Collect the rangeElements to be processed
while (rangeElement = paragraph.findText(searchPattern, rangeElement)) {
if (rangeElement != null && rangeElement.getStartOffset() != -1) {
const element = rangeElement.getElement();
if (element.getAttributes(rangeElement.getStartOffset()).BACKGROUND_COLOR === backgroundColor) {
rangeElements.push(rangeElement)
}
}
}
}
});
// Process the collected rangeElements in reverse order (makes things easier when deleting content)
rangeElements.reverse().forEach(r => {
if (r != null && r.getStartOffset() != -1) {
const element = r.getElement();
// Remove text
element.asText().deleteText(r.getStartOffset(), r.getEndOffsetInclusive())
// Remove highlighting
// element.setAttributes(textLocation.getStartOffset(), textLocation.getEndOffsetInclusive(), style);
}
});
}

Doesn´t recognice as equal (==)

Can someone tell me why these variables marked with red are not recognized as equal (==).
Google Apps Script is Javascript-based. In Javascript, you can not compare two arrays using ==.
One method is to loop over both arrays and to check that the values are the same. For example you can include the function:
function compareArrays(array1, array2) {
for (var i = 0; i < array1.length; i++) {
if (array1[i] instanceof Array) {
if (!(array2[i] instanceof Array) || compareArrays(array1[i], array2[i]) == false) {
return false;
}
}
else if (array2[i] != array1[i]) {
return false;
}
}
return true;
}
And then update the line in your code from if (responsables == gestPor) { to if (compareArrays(responsables, gestPor)) {
For other methods of comparing arrays in Javascript, see this answer.
It is because you are comparing arrays. If you are just getting a single cell value, use getValue() instead of getValues()
To make things work, change these:
var gestPor = hojaActivador.getRange(i,13,1,1).getValues();
var responsables = hojaConMails.getRange(1,n,1,1).getValues();
to:
var gestPor = hojaActivador.getRange(i,13).getValue();
var responsables = hojaConMails.getRange(1,n).getValue();
Do these to all getValues() where you're only extracting 1 cell/value.
See difference below:

How to search and replace a horizontal rule and linebreaks

I need to automatically delete all horizontal rules which are surrounded by 6 linebreaks (3 before and 3 after) on a google doc.
This piece of code seems to put in the logs the correct linebreaks I want to delete (that's a first step) :
function myFunction() {
var doc = DocumentApp.getActiveDocument();
var body = doc.getBody().getText();
var pattern = /\s\s\s\s/g;
while (m=pattern.exec(body)) { Logger.log(m[0]); }
}
I have two questions :
What tool can I use to delete these linebreaks (I don't yet understand the subtilies of using replace or replaceText, all my attemps with these have failed) ?
How can I add to my var pattern (the pattern to be deleted) a horizontal rule ? I tried /\s\s\s\sHorizontalRule\s\s\s\s/g but of course it did not work.
Horizontal rule is an element inside a paragraph (or sometimes inside a list item). Since it is not text, it can not be found or replaced by means of a regular expression. We should search for objects which are specially arranged in the document body, and delete them if found.
Consider the following code example:
function deleteHR() {
var body = DocumentApp.getActiveDocument().getBody();
var hr = null, hrArray = [], countDeleted = 0;
// Collect all horizontal rules in the Document
while (true) {
hr = body.findElement(DocumentApp.ElementType.HORIZONTAL_RULE, hr);
if (hr == null) break;
hrArray.push(hr);
}
hrArray.forEach(function(hr) {
var p = hr.getElement().getParent();
// Get empty paragraphs as siblings (if any)
var prevSiblings = getSiblings(p, 3, true),
nextSiblings = getSiblings(p, 3, false);
// Define a short function for batch deleting items
function remove(e) {
e.removeFromParent();
}
// If empty paragraphs exist (3 before and 3 after)
if (prevSiblings.length == 3 && nextSiblings.length == 3) {
// then delete them as well as the rule itself
hr.getElement().removeFromParent();
prevSiblings.forEach(remove);
nextSiblings.forEach(remove);
countDeleted++;
}
});
// Optional report
Logger.log(countDeleted + ' rules deleted');
}
// Recursive search for empty paragraphs as siblings
function getSiblings(p, n, isPrevious) {
if (n == 0) return [];
if (isPrevious) {
p = p.getPreviousSibling();
} else {
p = p.getNextSibling();
}
if (p == null) return [];
if (p.getType() != DocumentApp.ElementType.PARAGRAPH) return [];
if (p.asParagraph().getText().length > 0) return [];
var siblings = getSiblings(p, n - 1, isPrevious);
siblings.push(p);
return siblings;
}
Main function deleteHR() does all the work. However it appears helpful to use another separate function getSiblings() for recursive search for empty paragraphs. May be, this way is not the only, but it works.

Find existing bookmark folder by title?

I am trying to bring back the folder id of any bookmark folder which has a title matching a given string.
Problem is it doesn't return back folder ids when the text is the same :C
This is my code:
chrome.bookmarks.getTree(function(bookmarks)
{
search_for_url(bookmarks, "herpaderp");
});
function search_for_title(bookmarks, title)
{
for(i=0; i < bookmarks.length; i++)
{
if(bookmarks[i].url != null && bookmarks[i].title == title)
{
// Totally found a folder that matches!
return bookmarks[i].id;
}
else
{
if(bookmarks[i].children)
{
// inception recursive stuff to get into the next layer of children
return search_for_title(bookmarks[i].children, title);
}
}
}
// No results :C
return false;
}
There are two problems with your search_for_title function.
The variable i must be local. To make it a local variable, you have to use var i = 0 instead of i = 0 in the for statement.
search_for_title returns false when it can't find a bookmark that has the specified title, but you still need to look into the next item, so after recursively calling search_for_title, you return the return value only if the bookmark has been found. Otherwise, search should be continued instead of returning false.
Here's the code I tested to run correctly:
function search_for_title(bookmarks, title)
{
for(var i=0; i < bookmarks.length; i++)
{
if(bookmarks[i].url != null && bookmarks[i].title == title)
{
// Totally found a folder that matches!
return bookmarks[i].id;
}
else
{
if(bookmarks[i].children)
{
// inception recursive stuff to get into the next layer of children
var id = search_for_title(bookmarks[i].children, title);
if(id)
return id;
}
}
}
// No results :C
return false;
}

how to compare two array collection using action script

how to compare two arraycollection
collectionArray1 = ({first: 'Dave', last: 'Matthews'},...........n values
collectionArray = ({first: 'Dave', last: 'Matthews'},...........n values
how to compare..if equal just alert nochange if not alert chaged
If you just want to know if they are different from each other, meaning by length, order or individual items, you can do the following, which first checks to see if the lengths are different, then checks to see if the individual elements are different. This isn't terribly reusable, it's left as an exercise for the reader to split this apart into cleaner chunks :)
public function foo(coll1:ArrayCollection, coll2:ArrayCollection):void {
if (coll1.length == coll2.length) {
for (var i:int = 0; i < coll1.length; i++) {
if (coll1[i].first != coll2[i].first || coll1[i].last != coll2[i].last) {
Alert.show("Different");
return;
}
}
}
Alert.show("Same");
}
/* elements need to implement valueOf
public function valueOf():Object{}
*/
public static function equalsByValueOf(
first:ArrayCollection,
seconde:ArrayCollection):Boolean{
if((first==null) != (seconde==null) ){
return false;
}else if(!first && !seconde){
return false;
}
if(first.length!=seconde.length){
return false;
}
var commonLength:int = first.length;
var dictionary:Dictionary = new Dictionary();
for(var i:int=0;i<commonLength;i++){
var item1:Object = first.getItemAt(i);
var item2:Object = seconde.getItemAt(i);
dictionary[item1.valueOf()]=i;
dictionary[item2.valueOf()]=i;
}
var count:int = 0;
for (var key:Object in dictionary)
{
count++;
}
return count==commonLength;
}
/* valueOf sample
* something like javaObject.hashCode()
* use non changing fields(recommended)
*/
public function valueOf():Object{
return "_"+nonChangeField1+"_"+nonChangeField2+"...";
}
I was going to say this.
if(collectionArray === collectionArray1)
But that wont work (not triple = signs). As === is used to see classes.
I would write a function called check if object exists in array.
Create an array to hold elements that are not found. eg notFound
in Collection1 go through all the element and see if they exist in Collection2, if an element does not exist, add it to the notFound array. Use the function your created in step1
Now check Collection2, if an element is not found add it to the notFound array.
There is no 5.
Dude, use the mx.utils.ObjectUtil... the creators of actionscript have already thought about this.
ObjectUtil.compare(collection1, collection2) == 0;