1

Is there any way with Google Docs to insert the contents of one Google Doc into another? In Microsoft Word, there is the IncludeText field, where you simply link the other Word document you want to include and it gets included.

Use case: If you have, say, a custom header that you want to have at the beginning of every one of your files. If you just copy-paste the header into your file, then if you want to change it then you need to change it in each file, which is a massive pain. Ideally, you could just have {{header}} at the top of each file, and then you could activate something that inserts the other file.

My solution was to create a Google Apps Script that copies the current doc and replaces each instance of {{header}} with the contents of another Google Doc. This works, except that the only obvious way to do this only inserts plain text, and I need to insert in formatted text (and tables, lists, and inline images).

My current script:

function runReplace() {
  var currentDoc = DocumentApp.getActiveDocument();
  var currentBody = currentDoc.getBody();

  // Make a copy of the current doc with the name "[current doc name] filled"
  var newFile = DriveApp.getFileById(currentDoc.getId()).makeCopy(currentDoc.getName() + " filled");

  var newDoc = DocumentApp.openById(newFile.getId());

  var willReplaceWith = DocumentApp.openByUrl(URL_GOES_HERE);
  var replaceString = '{{header}}';
  newDoc.getBody().replaceText(replaceString, willReplaceWith.getBody().getText())
}

I'm thinking that perhaps the best solution to have formatted text will be to find all the locations of the string in the paragraph, and for each of them, go through each element of the header doc, and call each specific insert function on the body of the new doc.

This StackOverflow answer shows a little bit of what would be involved in that. While it certainly wouldn't be impossible, it seems like there should be a much better way to do it.

EDIT:

This version seems like my best solution. There are still a few issues, but it's basically doing what I want:

function runReplace() {

  var currentDoc = DocumentApp.getActiveDocument();

  // Make a copy of the doc with the name "Filled: [current doc name]"
  var newFile = DriveApp.getFileById(currentDoc.getId()).makeCopy("Filled: " + currentDoc.getName());

  var targetDoc = DocumentApp.openById(newFile.getId());

  var targetBody = targetDoc.getBody();

  var sourceDoc = DocumentApp.openByUrl(URL_GOES_HERE);
  var replaceTag = '{{KEY}}';

  // Find where to copy to
  var replaceTagRangeElement = targetBody.findText(replaceTag);
  var replaceTagElement = replaceTagRangeElement.getElement();
  var replaceTagParent = replaceTagElement.getParent();
  var replaceTagLocation = replaceTagParent.getParent().getChildIndex(replaceTagParent);

  var replaceInto = replaceTagParent.getParent();

  var replaceIntoLocation = replaceTagLocation + 1;

  // Copy into the target document
  try {
    for (var j = 0; j < sourceDoc.getBody().getNumChildren(); ++j) {
      var element = sourceDoc.getChild(j).copy();
      var type = element.getType();

      // Insert whichever thing needs to be inserted
      if (type == DocumentApp.ElementType.INLINE_IMAGE) {
        replaceInto.insertImage(replaceIntoLocation, element);
      } else if (type == DocumentApp.ElementType.PARAGRAPH) {
        if (j == 0 && replaceTagParent.getType() == DocumentApp.ElementType.LIST_ITEM) {
          // If this is a tag inside of a list item
          //  the first paragraph is added as text for the list item
          replaceTagParent.appendText(element.getText());
          replaceIntoLocation--;
        } else {
          var par = replaceInto.insertParagraph(replaceIntoLocation, element);
          // Merge the first paragraph with the previous one 
          //  so that tags don't start with newlines
          if (j == 0 && par.getPreviousSibling().getType() == DocumentApp.ElementType.PARAGRAPH) {
            par.merge();
            replaceIntoLocation--;
          }
        }
      } else if (type == DocumentApp.ElementType.TABLE) {
        replaceInto.insertTable(replaceIntoLocation, element);
      } else if (type == DocumentApp.ElementType.LIST_ITEM) {
        replaceInto.insertListItem(replaceIntoLocation, element);
      }
      replaceIntoLocation++;
    }
  } catch (e) {
    Logger.log(e);
  }

  var replaceTagRangeElement = targetBody.findText(replaceTag);
  var replaceTagElement = replaceTagRangeElement.getElement();
  replaceTagElement.replaceText(replaceTag, "");

}
gbear605
  • 89
  • 1
  • 10

1 Answers1

1

The solution is to either make your HTML into a blob and insert or have a source document that is tagged with the formatted code you want to insert into your target document.

Tagged Source Document Solution.

  1. Add an opening and closing tag around your source code.
  2. Search for the opening tag and get its location.
  3. Search for the closing tag and get its location.
  4. Copy all the code between the tags.
  5. Remove the tags and add into source document.

Possible Code

   function findTag(){ 

   var thisDoc = DocumentApp.getActiveDocument();
   var targetDoc = DocumentApp.getActiveDocument();
   try{
    var sourceDoc = DocumentApp.openById(FILEID); 
   } catch (e) {
    return e;
   }

   var startTag = "<<start>>";     
   var firstTag = sourceDoc.getBody().findText(startTag); 
   var startTagElement = firstTag.getElement();   
   var parent = startTagElement.getParent();
   var startPoint = parent.getParent().getChildIndex(parent);  

   var endTag = tag.replace('start', 'finish'); 
   var lastTag = sourceDoc.getBody().findText(endTag);
   var endTagElement = lastTag.getElement();
   var endParent = endTagElement.getParent();
   var endPoint = endParent.getParent().getChildIndex(endParent);

   // copy into the target document
   try {
      for( var j = startPoint; j <= endPoint; ++j ) {    
        var body = targetDoc.getBody();
        var element = sourceDoc.getChild(j).copy();
        var type = element.getType();

        if( type == DocumentApp.ElementType.INLINE_IMAGE ){
          body.insertImage(start, element);          
        }      
        else if( type == DocumentApp.ElementType.PARAGRAPH ){    
          body.insertParagraph(start, element);      
        }

        else if( type == DocumentApp.ElementType.TABLE){        
          body.insertTable(start, element);
        }       
        else if( type == DocumentApp.ElementType.INLINE_IMAGE ){ 
          var blob = body.getChild(j).asInlineImage().getBlob();
          body.insertImage(start, blob); }

        else if( type == DocumentApp.ElementType.LIST_ITEM){
          body.insertListItem(start, element);
        }
        start ++;
      }
    }
    catch(e){
    return e;   
    }        
     // Delete the tags
     body.replaceText(endTag, ""); 
     body.replaceText(startTag, "");
}
Jason Allshorn
  • 1,625
  • 1
  • 18
  • 27