How to I copy one google doc into another - google-apps-script

I am wondering how to copy a google doc, but include the style, links and font, from within google scripts.
I have tried to use the getBody()
DocumentApp.getActiveDocument().getBody().clear()
document2 = DocumentApp.openById("Document_ID").getBody().getText()
DocumentApp.getActiveDocument().getBody().appendParagraph(document2)
But that only copies that raw text.
EDIT:
Removed documents because the question was solved

Your problem is that getText will only return the raw text, nothing more. Treat them as objects instead, iterate, then append one by one.
function appendContents() {
var source = DocumentApp.getActiveDocument().getBody();
var destination = DocumentApp.openById("DOC ID");
var numElements = source.getNumChildren();
for (var i = 0; i < numElements; ++i ) {
var body = destination.getBody()
var element = source.getChild(i).copy();
var type = element.getType();
if( type == DocumentApp.ElementType.PARAGRAPH ){
body.appendParagraph(element);
}
// Add other element types if you are expecting other elements
// e.g. LIST_ITEM, TEXT, TABLE
// Note that different append will be used for each element type.
}
destination.saveAndClose();
}
If you want to add other element types, see list of available ElementTypes

Related

google drive app script, positioned images duplicating on last page of every document when merging documents

I'm creating a google API append script. There's a google sheet with lots of google document links. I retrieve these links open the file, go through its content and append it to a new document. This needs to be done for all documents in this list. After struggling for a while I got all of it working except for positioned images. Either I don't get an image at all, or the image gets copied in, but from that point on is copied and pasted on every page of the document. I'm probably just failing somewhere in the coding process but can't seem to find my own flaw. Note that I work in IT but not as a developer so Yes the code is written very poorly :'(
I've tried and reeditted my code repeatedly to ensure i was picking up the positioned image properly (e.g. by putting text in instead of the image etc. and it all seems to work properly, however the positioned image keeps repeating all over the document.)
Note that below I've only included the bad function and the main i'm calling, not all the get stuff from the excel sheet as that's working properly.
UPDATE:
I've updated my code. It now successfully finds, sets layout etc. and places the positioned images.
However, from that point on, the positioned images that have been placed earlier in the merged document are repeated on the last paragraph of every appended document.
function main() {
var spreadsheetid = "Fill_this_with_spreadsheet_ID";
var DocIDS = [];
DocIDS = getDocIDFromSpreadsheet(spreadsheetid);
mergeGoogleDocs(DocIDS);
//remove_blank(DocIDS);
}
/*Function to append contents of document to another document
for (var i = 1; i < List.length; ++i ) {
var otherBody = DocumentApp.openById(List[i]).getActiveSection();
var totalElements = otherBody.getNumChildren();
baseDoc.saveAndClose()
var baseDoc = DocumentApp.openById(DocIDS[0]);
var body = baseDoc.getActiveSection();
for( var j = 0; j < totalElements; ++j ) {
var element = otherBody.getChild(j).copy();
var type = element.getType();
if( type == DocumentApp.ElementType.PARAGRAPH ){
var positionedImages = element.getPositionedImages();
if ( positionedImages == "PositionedImage") {
//This is required because the positionedImage needs a "new" pragraph to anchor to...
var paragraph = body.appendParagraph("");
var tempvalue = positionedImages[0].getLayout();
var tempheight = positionedImages[0].getHeight();
var tempwidth = positionedImages[0].getWidth();
var tempblob = positionedImages[0].getBlob();
paragraph.addPositionedImage(tempblob);
var positionedImages2 = paragraph.getPositionedImages();
positionedImages2[0].setLayout(tempvalue);
positionedImages2[0].setLayout(tempvalue);
positionedImages2[0].setHeight(tempheight);
positionedImages2[0].setWidth(tempwidth);
//To-do, fix repeated multiplication of positioned Images on all following documents at the final paragraph.
}
else {
body.appendParagraph(element);
}
}
else if( type == DocumentApp.ElementType.TABLE ){
body.appendTable(element);}
else if( type == DocumentApp.ElementType.LIST_ITEM ) {
glyphType = element.getGlyphType();
body.appendListItem(element);
element.setGlyphType(glyphType);
}
else
throw new Error("Unknown element type: "+type);
}
body.appendPageBreak()
}
I expect to get all documents merged into one document. while it does merge everything, it goes wrong on positioned images. When I run the code I get the positioned images duplicated on every new appended document following the first time it was inserted. Where it only needs to be included once, not on every document.
I've been struggling for several evenings now and desperate for pointers and/or a proper fix.

How to copy content and formatting between Google Docs?

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

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.

Copy text, images, tables, ALL formatting, margins from on GDoc to another

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

Replace text in Google Doc table for form merge

I am trying to make automated reports by filling a form. The data is read out by the script from the last spreadsheet line. Then the report is made as a Google Doc. Here some tags inside this document present where the items should be. %meterx% is for meters.
These can be images or text. For normal paragraphs, this is working fine with the first loop where the type = paragraph. But it skips tables. I need to replace the %meterx% in a table cell with the image, just like I do with the paragraph, but I am stuck at the code to look through the table.
I see some ways to replace text but this seems to be the only way to replace it with images.
var totalElements = doc.getNumChildren();
var el=[]
for( var j = 0; j < totalElements; ++j ) {
var element = doc.getChild(j);
var type = element.getType();
if (type =='PARAGRAPH'){
el[j]=element.getText()
if(el[j]=='%meter3%'){element.removeFromParent();
var newimage = UrlFetchApp.fetch('http://chart.googleapis.com/chart?chf=bg,s,67676700&chs=280x150&cht=gm&chds=0,10&chd=t:'+row[4]+'&chdlp=b').getBlob();
doc.insertImage(j, newimage);
if (type =='TABLE'){
var tablerows=element.getNumRows();
Logger.log(tablerows);
for ( var i = 0; i < tablerows; ++i ) {
var tablerow = element.getRow(0)
Logger.log(tablerow); // <--- gives TableRow
} /// STUCK !! :)
A TableRow contains child elements, of type TableCell, which also contain child elements. Those are type PARAGRAPH if they are blank or contain text.
You can access the text within a TableCell with getText(), but it's a good idea to confirm that the cell contains text first.
The code below handles PARAGRAPH and TABLE element types, and for tables it explores the TABLECELL elements. I don't know what you're doing with your array el[], so I've left that out, and also commented out the image-replacement code - instead, I'm just logging the structure and content of the table for illustration. To complete your goal, you should replace the logging with the same match & replace behavior you have for PARAGRAPH.
NOTE: I am using a couple of helper functions not detailed here, which should be self-explanatory, getFileByName_() and elementTypeToText_().
function Q13869576() {
var folder = "StackOverflow";
var docname = "Q13869576.gdoc";
var docId = getFileByName_(folder, docname).getId();
var doc = DocumentApp.openById(docId);
var docBody = doc.getActiveSection();
var totalElements = doc.getNumChildren();
var el=[]
for( var j = 0; j < totalElements; ++j ) {
var element = doc.getChild(j);
var type = element.getType();
switch (type) {
case DocumentApp.ElementType.PARAGRAPH:
el[j]=element.getText()
if(el[j]=='%meter3%'){
Logger.log( "Found tag in paragraph" );
// element.removeFromParent();
// var newimage = UrlFetchApp.fetch('http://chart.googleapis.com/chart?chf=bg,s,67676700&chs=280x150&cht=gm&chds=0,10&chd=t:'+row[4]+'&chdlp=b').getBlob();
// doc.insertImage(j, newimage);
}
break;
case DocumentApp.ElementType.TABLE:
var tablerows=element.getNumRows();
Logger.log(tablerows);
for ( var row = 0; row < tablerows; ++row ) {
var tablerow = element.getRow(row)
for ( var cell=0; cell < tablerow.getNumCells(); ++cell) {
Logger.log( "Table Row("+row+") Cell ("+cell+")");
Logger.log(
elementTypeToText_(tablerow.getChild(cell).getType())
+" with child type "
+elementTypeToText_(tablerow.getChild(cell).getChild(0).getType()));
var celltext = tablerow.getChild(cell).getText();
Logger.log( "Text is ("+celltext+")" );
}
}
break;
}
}
}
This is an excerpt of the logs from a sample doc, with a table containing a cell with the %meter3% tag:
...
Table Row(1) Cell (2)
TABLE_CELL with child type PARAGRAPH
Text is (%meter3%)
...