I am writing a google apps script that places the paragraphs of one document into a dictionary, then inserts them into a set of other documents based on certain criteria. The paragraphs are extracted like so:
var totalElements = doc.getNumChildren();
for( var j = 0; j < totalElements; ++j ) {
var element = doc.getChild(j).copy();
var type = element.getType();
// logic to add element to dictionary
}
Later, once the location a paragraph should be inserted is found, it is inserted via the insertParagraph() function:
body.insertParagraph(paragraphIndex, element);
This works fine, except for the fact that some paragraphs have comments attached to them that need to be copied along with them, and this method does not keep comments attached to their elements. Is there any way to implement this? Thanks for any help!
Related
Some time ago, Google Sheets changed the adding of links to rich text, and now links cannot be found at the formula anymore. Personally I dislike this change very much because I use a custom function that extracts hyperlinks from cells with the old formula, and with the new change I just cannot find a way of doing this. I am not very good with functions yet, mainly the reason why I wrote my question title as detailed as possible.
What I need to do is to extract hyperlinks from cells using a custom formula, since it will need to be used among many other vanilla formulas. How can I set up a custom function/formula to extract the new hyperlinks based on range?
Here are the sheets where I want to extract links:
https://docs.google.com/spreadsheets/d/1JnSKQ7nd4J3NPRH4uSsOCYms-DF16j1pkCAuJeikToo/edit#gid=317867416
I would like to extract links from the games that are being posted, because I need to use those links elsewhere and I'd also like to have them ready to be imported if ever needed.
I need to specify a formula in another cell which will extract those links. For example =GETURL(B6) which would extract the new rich text hyperlinks based on a range that I insert for it.
Alternatively, is it possible to configure the document so that it makes links in the old format whenever inserted? This way I could try to workaround the new settings and future inserted links would go into the =HYPERLINK formula instead.
Many thanks!
I think this script would come in handy.
It gives the possibility to retrieve back the URL from a hyperlink formula.
Go to script editor, and create a new project.
Save the file.
Head up to Run > linkURL to run the script. This will create a new function in Sheets.
Let’s say cell A1 has the hyperlink in it. Go to any cell and type =linkURL(A1), and then hit Enter.
function linkURL(reference) {
var sheet = SpreadsheetApp.getActiveSheet();
var formula = SpreadsheetApp.getActiveRange().getFormula();
var args = formula.match(/=w+((.*))/i);
try {
var range = sheet.getRange(args[1]);
}
catch(e) {
throw new Error(args[1] + ' is not a valid range');
}
var formulas = range.getFormulas();
var output = [];
for (var i = 0; i < formulas.length; i++) {
var row = [];
for (var j = 0; j < formulas[0].length; j++) {
var url = formulas[i][j].match(/=hyperlink("([^"]+)"/i);
row.push(url ? url[1] : '');
}
output.push(row);
}
return output
}
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)
Fellow Stackers. I'm using a Google Apps Script to (a) capture all "Comments" in a Google Document...
...and (b) list them in a column of a Google Sheet...
However, I'm wondering if it's possible to...
(1) Array "Comments" into individual cells down a column in my Sheet rather than into a single column, as I have now. This is the bit of GAS I'm currently using to grab comment contents:
var comments = JSON.parse(Drive.Comments.list(id));
var items=comments.items;
var string = "";
for(var i in items){
string+='\n';
string+=items[i].content;
}
(2) Order the "Comments" by anchor position in my Google Document—i.e. the comment anchored highest in the doc would appear in the first cell of the Sheet's column.
(3) Also include "Suggested Edits" from my Google Document alongside the comments. Can those be accessed via API yet?
Thanks in advance to anyone who may be able to help!
Google Document: https://docs.google.com/document/d/1O7zAdkCmxhYihtfJhZ3OGkWfO8UUJ_deoHEYr7rQHW4/edit?usp=sharing
Google Apps Script: https://script.google.com/macros/d/1MgTtU0cKSS_XghRjAMtjZFQAdsbU9SkD_2zx03KVKb1Vy4iBBp3MI2QW/edit?uiv=2&mid=ACjPJvHY-vp53Ek1wBR4-W3Q1Ur8dSdyN0g6ZI7n3I48-e7EWyq6v9gY82OAeVNlnpQBbY3ICOzi4PCRtp-pjuqAbH3oePLelcIp-YUPs2FNbB7Cl7CC-AvgnoJPcXCnrO8CrIJEI2v8ns8&splash=yes
Google Sheet: https://docs.google.com/spreadsheets/d/1uAtmAO0we7h3HUAFlDBLXlShSLvHVM_W6OSXPwpX_t8/edit?usp=sharing
(1) Array "Comments" into individual cells down a column in my Sheet rather than into a single column, as I have now.
This bit of code is taking an array of comments, and concatenating them into a single string:
var string = "";
for(var i in items){
string+='\n';
string+=items[i].content;
}
To be able to put each comment into a separate cell in a column, you need to change that array into a 2-dimensional array, with each of the original elements in its own "row". Something like:
var data = []; // start with an empty array
for (var i=0; i<items.length; i++) {
var item = items[i]; // current comment
// A row is an array of cells
var row = [item.htmlContent,item.author.displayName,item.createdDate];
data.push(row); // Add this row to the data array
}
This line writes the content of a single cell, albeit using setValues() which can fill a rectangular range:
var targetRange = sheet.getRange(lastRow+1,1,1,1).setValues([[string]]);
With the 2-D array created earlier, you can append to the sheet like so:
var targetRange = sheet.getRange(lastRow+1,1,data.length,data[0].length);
targetRange.setValues(data);
Result:
function driveApiComment(id){
var comments = JSON.parse(Drive.Comments.list(id));
var items=comments.items;
var data = []; // start with an empty array
for (var i=0; i<items.length; i++) {
var item = items[i]; // current comment
// A row is an array of cells
var row = [item.htmlContent,item.author.displayName,item.createdDate];
data.push(row); // Add this row to the data array
}
var sheet = SpreadsheetApp.openById(submissionSSKey).getSheets()[0];
var lastRow = sheet.getLastRow();
var targetRange = sheet.getRange(lastRow+1,1,data.length,data[0].length);
targetRange.setValues(data);
}
(2) Order the "Comments" by anchor position in my Google Document—i.e. the comment anchored highest in the doc would appear in the first cell of the Sheet's column.
You're out of luck (for now, at least). See:
How to match comments on an image using kix anchor (or not) in Google Docs
Anchor documentation does not exist?
Creating anchored comments programmatically in Google Docs
Summary: Google's anchors are not decipherable. (Likely they are a key to a hidden database that includes the actual line & char refs to your document, along with your social security number and mother's maiden name.) You could retrieve them & sort them alphabetically... but that would have no relation to where the comments appear in a document.
(3) Also include "Suggested Edits" from my Google Document alongside the comments. Can those be accessed via API yet?
Nope.
I have sucessfully written a small script, which creates a serial letter (physical letter to several recipients) based on data in a Google spreadsheet creating a new document for each letter/addresse.
It works, but for large mailings this approach is a bit cumbersome as a large amount of documents are created and need to be printed individually.
Now i would like to do the same but as a result having all of the letters in one single Google document.
Is there any way to copy the content of an existing document and inserting it a number of times into the same or any other documents (i.e. copy/paste via apps script)?
Following your comment, here is the full code I use to merge an undetermined number of docs in a new one.
All document IDs are in an array of IDs as argument for the main function, the results is a new doc with "multi-page" appended to the name. If you need more explanation than provided by the in code comments just let me know... (note that it will work only for documents containing text and tables, if you have images ot other data type you'll have to handle that case in the main loop where we check the ElementType following the same logic)
EDIT : first code removed, following your update I tried this approach assuming you have only paragraphs in your master doc... give it a try and I guess you could start from there to developp your project.
function Serialletter_Singledocument() {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Datenbank");
var LastColumn = sheet.getLastColumn();
//here you need to get document id from url (Example, 1oWyVMa-8fzQ4leCrn2kIk70GT5O9pqsXsT88ZjYE_z8)
var FileTemplateFileId = "1Wrf2qvUTyc5tMmJIly40Z4U4sJb4-QhT5z-UfJmtQ-M" //Browser.inputBox("ID der Serienbriefvorlage (aus Dokumentenlink kopieren):");
var doc = DocumentApp.openById(FileTemplateFileId);
var DocName = doc.getName();
var headpara=' ***** ';
// Fetch entire table containing data
var data = sheet.getDataRange().getValues();
//Create copy of the template document and open it
var SerialLetterID = DocsList.getFileById(FileTemplateFileId).makeCopy(DocName +" Serienbrief").getId();
var docCopy = DocumentApp.openById(SerialLetterID);
var totalParagraphs = docCopy.getBody().getParagraphs() ;// get the total number of paragraphs elements
Logger.log(totalParagraphs);
var elements = [];
for ( var i = 1; i < data.length; i++) { //do for every record in the spreadsheet (containing the content to replace the variables in the letter)
for (var e=0;e<totalParagraphs.length;e++){
var element = totalParagraphs[e].copy();
// Logger.log(element.editAsText().getText())
for(var c=0;c<data[0].length;c++){
element.replaceText("<" +data[0][c] +">", data[i][c]); //replace variable (from column title) with actual value
}
elements.push(element);// store paragraphs in an array
}
for(var el=0;el<elements.length;el++){
var paragraph = elements[el].copy();
docCopy.getBody().appendParagraph(paragraph);
}
docCopy.getBody().appendPageBreak()
}
docCopy.saveAndClose();
Browser.msgBox("Serienbrief ist erstellt. Sie finden die erstellten Dokumente in Google Drive unter Meine Ablage");
}
First, thanks to Serge insas for enough help to even write this script!
The script runs, but doesn't give me anything like the desired result.
What I want to do is
(1) Create a new document in a given folder(Let's call it 'myfolder') and write a title for it.
That works--sort of. I get a document, but it's icon is a tiny image for a doc file but with its corner turned over. It only opens in the viewer. It does have the title, but nothing that I tried to write to it subsequently.
(2) Get an array of all the files in 'myfolder'. That's where the Serge's help came in. They need to be converted into readable documents. I THINK that worked (More on this later). At least the debugger did not throw an error. I ended up with a 'contents' array.
(3) For each of those documents, get the file name and extract the Table of Contents. Append each of those to the doc created in (1). To do this, I used a for loop which iterated from one to contents.length. Now the FIRST problem arose. Whatever contents.length brought back wasn't right because the next loop, where the processing occurred errored out at approximately the number of documents, not counting these little half-docs the script generated. I got around this by a try-catch construction that stopped when it hit an undefined file. That and the execution transcript suggested that the script did go through that loop.
So here's the BIG problem. Whatever those little half-docs were, nothing other than the first introductory line was written to them.
The debugger is useless.
(a) It doesn't show me the log file. Just a date.
(b) If I place a breakpoint, it MAY stop at it, but I see mostly a list of objects. If I click on the + sign, I get a list of methods. A few of the non-object variables are shown. It's impossible to step through the code because it can take up to three minutes to go from one line to the next, so it's been rather tough to debug this.
The only info I could garner was that the execution transcript did suggest that I did get doc objects from the files.
I think the problem is with a*doc = DocumentApp.openById(docObject[jj]);* which is in bold in the code below.
Sorry for the strange error handling; I was trying to get some insight into what was wrong. Alas, the debugger tells me that Err is an string and gives me no value.
Whether the rest of the code works or not, I can't tell if it does, indeed error out the first time it hits the bolded line.
Thanks for your patience.
function listDocTOCsInFolder()
{
// Thanks to crucial help from Serge insas via Stack Overflow.
var afolder; // Folder you want to work on
var contents; // Files in the folder
var TOCListDoc; // Document you will create to hold your TOC List
var docObject = []; // Holds a list of documents created from contents
var aname;
var adoc;
var err = "";
var isErr = false;
TOCListDoc = DocsList.createFile("TOCList", "Document TOC List");
afolder = DocsList.getFolderById("0B-UcimyrHLl2bm1OanExaHotc2M")
//Can't figure out what exactly constitutes the path of a folder.
TOCListDoc.addToFolder(afolder);
// Get all the document files in your named folder. Unfortunately,they are not document objects
var contents = afolder.getFilesByType(DocsList.FileType.DOCUMENT);
// This loop gives you an array of DocumentApp objects.
for (var ii = 0; ii < contents.length; ii++)
{
docObject.push(DocumentApp.openById(contents[ii].getId()));
}
// Now you can do a for loop to gather up the contents into one document.
Logger.log(contents.length);
// It isn't clear what this actually gets, because unless I set a trap of
// undefined documents, the loop keeps right on going.
var len = docObject.length;
var jj = 0;
for(jj = 0; jj < len; II++ )
{
try
{
**adoc = DocumentApp.openById(docObject[jj]);**
}
catch(err)
{
isErr = true;
}
if(!isErr)
{
// Get the information you want to write to your list doc
var TOC = adoc.getAs(DocumentApp.ElementType.TABLE_OF_CONTENTS);
logger.log(TOC);
aname = adoc.getName();
Logger.log(aname);
body.appendParagraph(counter, name);
body.appendParagraph(TOC);
//.setHeading(DocumentApp.ParagraphHeading.HEADING1);
}
else
{
Logger.log("Errored out");
}
}
}
when you say
"For each of those documents, get the file name and extract the Table
of Contents. Append each of those to the doc created in (1)
that's indeed what you should do... you used this code :
// Get the information you want to write to your list doc
var TOC = adoc.getAs(DocumentApp.ElementType.TABLE_OF_CONTENTS);
logger.log(TOC);
but doing this you assume that the first element in this document is necessarily the TOC and I'm not sure you can do that !!
What I I would try is to iterate through all the document's elements and check the type of each element, then copy the one that is a TOC to your new document.
See eventually this post to check how you could iterate into the document's elements and check their types, the purpose of this script was different but I guess the approach should help you.
Good luck
instead of
for(jj = 0; jj < len; II++ ) try for(jj = 0; jj < len; jj++ ) instead
and: read again the troubleshooting guide...