I want to write a Google Docs script that copies a template (which just contains a container bound script) and appends the contents of another document chosen by the user. How would I accomplish this? I already have a way to select the file (the template has a static id), but figure out a way to copy all the content of the document (including inlineImages and hyperLinks) to the my new document.
I guess the only way is to copy elements one by one... there are a whole bunch of document elements but it shouldn't be too hard to be quite exhaustive.
Here is how it goes for the most common types, you'll have to add the other ones.
(original code borrowed from an answer by Henrique Abreu)
function importInDoc() {
var docID = 'id of the template copy';
var baseDoc = DocumentApp.openById(docID);
var body = baseDoc.getBody();
var otherBody = DocumentApp.openById('id of source document').getBody();
var totalElements = otherBody.getNumChildren();
for( var j = 0; j < totalElements; ++j ) {
var element = otherBody.getChild(j).copy();
var type = element.getType();
if( type == DocumentApp.ElementType.PARAGRAPH )
body.appendParagraph(element);
else if( type == DocumentApp.ElementType.TABLE )
body.appendTable(element);
else if( type == DocumentApp.ElementType.LIST_ITEM )
body.appendListItem(element);
else if( type == DocumentApp.ElementType.INLINE_IMAGE )
body.appendImage(element);
// add other element types as you want
else
throw new Error("According to the doc this type couldn't appear in the body: "+type);
}
}
Related
I have followed this script How to copy content and formatting between Google Docs? to copy the content from one google doc and paste it to another, which works great, however everytime the content is pasted, there is a space on top of the pasted content, see below. How can the content be pasted properly?
Source file: https://docs.google.com/document/d/1xVpJM4hSN3fosFXR16JbZ1_7r0_PxV92T-G24X5LQRo/edit
Target file: https://docs.google.com/document/d/1g9oon4e0FDBF2fbexVCR-uxKko3B6-Hpj850kiH3qXo/edit
Basically the table from the source file will get copied and pasted to the target file for multiple times, and the tables should sit side by side on the target file without space on top which breaks the format.
appscript is inbedded in the source file
function copyDoc() {
var sourceDoc = DocumentApp.getActiveDocument().getBody();
// var targetDoc = DocumentApp.create('CopyOf'+DocumentApp.getActiveDocument().getName());
var targetDoc = DocumentApp.openById('1g9oon4e0FDBF2fbexVCR-uxKko3B6-Hpj850kiH3qXo');
var totalElements = sourceDoc.getNumChildren();
for( var j = 0; j < totalElements; ++j ) {
var body = targetDoc.getBody()
var element = sourceDoc.getChild(j).copy();
var type = element.getType();
if( type == DocumentApp.ElementType.PARAGRAPH ){
body.appendParagraph(element);
}
else if( type == DocumentApp.ElementType.TABLE){
body.appendTable(element);
}
else if( type == DocumentApp.ElementType.LIST_ITEM){
body.appendListItem(element);
}
// ...add other conditions (headers, footers...
}
targetDoc.saveAndClose();
}
Edit1: Based on TheWizEd's answer, here's how it looks like, format looks very off.
Why not copy the table cell into a new cell to the right. Try this.
First I copy the Source or template to a new file. Then I find the table and loop through each row duplicating the cell from column 1 to new column 2. If you wanted to add another row you could duplicate the first row as many times as you want to the new table.
However I notice you are using replacable text. You will have to have a different script to replace the text in column 1 vs. column 2.
function copyDoc() {
try {
let source = DocumentApp.getActiveDocument();
let file = DriveApp.getFileById(source.getId()).makeCopy("Target");
let target = DocumentApp.openById(file.getId());
let body = target.getBody();
for( let i=0; i<body.getNumChildren(); i++ ) {
let child = body.getChild(i);
if( child.getType() === DocumentApp.ElementType.TABLE ) {
child = child.asTable();
for( let i=0; i<child.getNumRows(); i++ ) {
let sRow = child.getRow(i)
let tRow = sRow.copy();
for( let j=0; j<tRow.getNumCells(); j++ ) {
let cell = tRow.getCell(j).copy();
sRow.appendTableCell(cell);
}
}
}
}
}
catch(err){
console.log(err);
}
}
Here is the result of my script.
This question already has answers here:
Created PDF does not reflect changes made to the original document
(2 answers)
Closed 3 months ago.
I have code that combines several Google Docs into one file, and code to save a Doc as PDF:
function createPDF(docId) {
var docFile = DriveApp.getFileById(docId);
var blob = docFile.getAs('application/pdf');
var file = DriveApp.createFile(blob);
file.setName('test.pdf');
}
function mergeDocuments(doc, docIDs){
var body = doc.getActiveSection();
for (var i = 0; i < docIDs.length; ++i ) {
var otherBody = DocumentApp.openById(docIDs[i]).getActiveSection();
var totalElements = otherBody.getNumChildren();
for( var j = 0; j < totalElements; ++j ) {
var element = otherBody.getChild(j).copy();
var type = element.getType();
if( type == DocumentApp.ElementType.PARAGRAPH )
body.appendParagraph(element);
else if( type == DocumentApp.ElementType.TABLE )
body.appendTable(element);
else if( type == DocumentApp.ElementType.LIST_ITEM )
body.appendListItem(element);
else
throw new Error("Unknown element type: "+type);
}
}
}
The mergeDocument() function works just fine to combine documents, and the createPDF() function also works fine - if I use them one-by-one. If I combine them into a single call, then the exported PDF is always blank.
function mergeAndCreatePDF() {
var doc = DocumentApp.create('test.doc');
var docIDs = ['docid0', 'docid1'];
mergeDocuments(doc, docIDs);
Logger.log(doc.getId())
var docFile = DriveApp.getFileById('' + doc.getId());
var blob = docFile.getAs('application/pdf');
var file = DriveApp.createFile(blob);
file.setName('test.pdf');
}
How can I combine the two methods so they can be used in a single can to Apps Script (rather than needing to run one, and then run the other)?
I think that happening because you're not saving and closing the docs.
Check this out https://developers.google.com/apps-script/reference/document/document#saveandclose
And try closing your doc at the end of the mergeDocuments function.
tehhowch have explained this in comment also, some credit goes to him too. :)
I needed a solution that copies into the current Doc the content of another set of Docs that are used as templates. (we need to retain the current docId). I wrote a simple Google Doc add-on that works like a charm... in some Google Apps domains.
I installed (and made screencasts) the IDENTICAL code in four different domains. In two of the domains the content copied from the selected "template" Doc gets (correctly) copied ONCE in the current Doc, however, in two other domains it gets injected TWICE!
All domains have identical settings (script run as admin, authorizations accepted, etc). I even installed a second copy in the faulty domains and that showed the same behaviour.
Anyone seen this behaviour?
function runInsert(template) {
var targetDoc = DocumentApp.getActiveDocument();
if( targetDoc.getHeader() == null) {
targetDoc.addHeader();
}/
if( targetDoc.getFooter() == null) {
targetDoc.addFooter();
}
var templateDoc = DocumentApp.openById(template);
// check for header, get elements and add to current doc
var templateHeader = templateDoc.getHeader();
if( templateHeader != null) {
var totalElementsHeader = templateHeader.getNumChildren();
for( var j = 0; j < totalElementsHeader; ++j ) {
var header = targetDoc.getHeader();
var element = templateHeader.getChild(j).copy();
var type = element.getType();
if( type == DocumentApp.ElementType.PARAGRAPH ){
header.appendParagraph(element);
}
}
}
// check for body, get elements and add to current doc
var templateBody = templateDoc.getBody();
if( templateBody != null) {
var totalElementsBody = templateBody.getNumChildren();
for( var j = 0; j < totalElementsBody; ++j ) {
var body = targetDoc.getBody();
var element = templateBody.getChild(j).copy();
var type = element.getType();
if( type == DocumentApp.ElementType.PARAGRAPH ){
body.appendParagraph(element);
Logger.log("j is " + j + " element contains " + element.getText());
}
else if( type == DocumentApp.ElementType.TABLE){
body.appendTable(element);
....
}
}
... copy footer
}
Found that the insert executed twice and fixed it in the sidebar javascript.
Does the script run off of installed triggers? I had a similar issue with one of my scripts where I accidentally installed a trigger twice which was leading to the script running twice for every event.
Check your triggers and see if multiples are installed for your project in the script editor under Resources > Current Project's Triggers.
I need to copy the content of a Google Document, and append it to another Document. If I use something like this:
newDoc.getBody().appendParagraph(template.getText());
...I get the text, but lose the formatting that was in my original file. (Bold, Italic, etc.)
How can I copy the contents and formatting to the new document? Is it possible to assign everything to one variable, and copy / paste it to the new document?
Not using only 1 variable , you'll have to iterate all the elements in the doc and copy them one by one.
there are multiple threads on the same subject, try for example this one : How to copy one or more existing pages of a document using google apps script
just read carefully the code and add all the content types that you are supposed to meet in your document (tables, images, pagebreaks...)
EDIT : here is a trial on that idea (to start with)
function copyDoc() {
var sourceDoc = DocumentApp.getActiveDocument().getBody();
var targetDoc = DocumentApp.create('CopyOf'+DocumentApp.getActiveDocument().getName());
// var targetDoc = DocumentApp.openById('another doc ID');
var totalElements = sourceDoc.getNumChildren();
for( var j = 0; j < totalElements; ++j ) {
var body = targetDoc.getBody()
var element = sourceDoc.getChild(j).copy();
var type = element.getType();
if( type == DocumentApp.ElementType.PARAGRAPH ){
body.appendParagraph(element);
}
else if( type == DocumentApp.ElementType.TABLE){
body.appendTable(element);
}
else if( type == DocumentApp.ElementType.LIST_ITEM){
body.appendListItem(element);
}
// ...add other conditions (headers, footers...
}
targetDoc.saveAndClose();
}
After trying a few mail merge scripts, I decided t write my own. My merge script runs as a separate fiIt reads a template from a GDoc, reads data from a GSpreadsheet, and merges it either into Gmails or into a new GDoc - one page / email per SS row.
The problem is that it doesn't copy text formatting, margins or images into the Gmail or new GDoc ... only the plain text.
I am using DocumentApp.openById > getActiveSection > getText() to capture the text.
Here is the code in GDoc http://goo.gl/fO5vP
I can't seem to share a script so I had to put it in a doc. Copy it to a new script and it will be color coded.
You should copy the template first using DocsList so you start with a "complete" initial document.
var template = DocsList.getFileById(docIDs[0]);// get the template model, in this sample I had an array of possible templates, I took the first one
var newmodelName=template.substr(0,11)+'multipage'+template.substring(18);// define a new name, do what you need here...
var baseDocId = DocsList.copy(template,newmodelName).getId();// make a copy of firstelement and give it new basedocname build from the serie(to keep margins etc...)
var baseDoc = DocumentApp.openById(baseDocId);// this is the new doc to modify
then use the document class that has a direct replaceText method
EDIT : about your secondary question, here is a suggestion on how you could do. It works nicely except for inlineImage, I'll keep looking at this. You could also make the script more universal by adding other element types...
function myFunction() {
var template = DocsList.getFileById(key);// get the template model
var newmodelName='testcopy';// define a new name, do what you need here...
var baseDocId = DocsList.copy(template,newmodelName).getId();// make a copy of firstelement and give it new basedocname build from the serie(to keep margins etc...)
var baseDoc = DocumentApp.openById(baseDocId);// this is the new doc to modify
var body = baseDoc.getActiveSection();
body.appendPageBreak();
var totalElements = body.getNumChildren();
for( var j = 0; j < totalElements; ++j ) {
var element = body.getChild(j).copy();
var type = element.getType();
if( type == DocumentApp.ElementType.PARAGRAPH )
body.appendParagraph(element);
else if( type == DocumentApp.ElementType.TABLE )
body.appendTable(element);
else if( type == DocumentApp.ElementType.LIST_ITEM )
body.appendListItem(element);
else if( type == DocumentApp.ElementType.INLINE_IMAGE )
{ var blob = body.getChild(j).asInlineImage().getBlob();
body.appendImage(blob); }
}
}
Edit 2 Thanks to #Fausto, here is a fully working version. Inline images are included in a paragraph so we had to dig one level more to get the blob...
function myFunction() {
var template = DocsList.getFileById(key);// get the template model
var newmodelName='testcopy';// define a new name, do what you need here...
var baseDocId = DocsList.copy(template,newmodelName).getId();// make a copy of firstelement and give it new basedocname build from the serie(to keep margins etc...)
var baseDoc = DocumentApp.openById(baseDocId);// this is the new doc to modify
var body = baseDoc.getActiveSection();
body.appendPageBreak();
var totalElements = body.getNumChildren();
for( var j = 0; j < totalElements; ++j ) {
var element = body.getChild(j).copy();
var type = element.getType();
if (type == DocumentApp.ElementType.PARAGRAPH) {
if (element.asParagraph().getNumChildren() != 0 && element.asParagraph().getChild(0).getType() == DocumentApp.ElementType.INLINE_IMAGE) {
var blob = element.asParagraph().getChild(0).asInlineImage().getBlob();
body.appendImage(blob);
}
else body.appendParagraph(element.asParagraph());
}
else if( type == DocumentApp.ElementType.TABLE )
body.appendTable(element);
else if( type == DocumentApp.ElementType.LIST_ITEM )
body.appendListItem(element);
}
}