Search code examples
google-apps-scriptgoogle-docsflysystem-google-drive

Get Header and Footer from template Google Doc, and apply to all docs in a Google Drive folder


I need help applying a header and footer that contains tables, paragraphs, and images (branding content/letterhead) across all Google docs in a Google drive folder.

GOALS:

  1. Get/Copy header from source document that contains tables, paragraphs, and images.
  2. Paste/Apply/Replace header copied in 1 to all documents in a Google drive folder
  3. Do same for Footer.
  4. Don't require programatic "appending" to existing footers.

I wish there was a way to do CSS or something similar for templates that could be applied to a batch of google docs... but I believe scripting is the only way.

This is adding on to this post: Find and Replace text in entire Google Drive folder, headers, footers, google docs using a script

function replaceHeaderAndFooter() {
  
 const headerToCopyandPaste = DocumentApp.openById("<SourceDocID>").getHeader().getTables().toString();  // ID contains source header
 const footerToCopyandPaste = DocumentApp.openById("<SourceDocID>").getHeader().copy();  // ID contains source footer
  
  
  var files = DriveApp.getFolderById("<FolderID>").getFiles();  //ID contains folder that has Google Docs that will have Header and Footer Replaced
  while (files.hasNext()) {
    var file = files.next();
    var doc = DocumentApp.openById(file.getId());
    
  var headerSectionToBeReplaced = doc.getHeader()
  var footerSectionToBeReplaced = doc.getFooter()
    
  headerSectionToBeReplaced.clear();
footerSectionToBeReplaced.clear();

 headerSectionToBeReplaced.appendTable(headerToCopyandPaste); //This does not work
 footerSectionToBeReplaced = footerToCopyandPaste    // This does not work
    
  }
}

Solution

  • I believe your goal and situation as follows.

    • In your situation, the destination Documents have multiple pages and the texts have already been set, and you want to copy the header and footer of the template document to all pages of each destination document as the overwrite.
    • From your shared Document,
      • I confirmed that in the header of the template document, the image is put in a table cell as a inline image.
      • I confirmed that in the footer of the template document, the image is used.
    • When the image is used in a table, even when the table is copied, it seems that the image is not copied.
      • I think that this might be the reason of your issue.
    • From your replying comments, I could confirm as follows.
      • There are hundreds number of Google Document files you want to use.
      • But you will run the script for every several Documents.

    In order to achieve your goal, in this sample script, I would like to propose the following flow.

    Flow:

    1. Retrieve Google Document files from the specific folder.
      • In this case, the Document files are retrieved from just under the specific folder without checking the subfolders.
      • Because from your replying comments, although there are hundreds number of Google Document files you want to use, you will run the script for every several Documents.
    2. Copy the header from the template Document to the destination Document.
    3. Copy the footer from the template Document to the destination Document.

    IMPORTANT:

    As an important point, in the current stage, unfortunately, it seems that when the header and footer retrieved by getHeader() and getFooter() of Google Document service cannot identify whether the check of 1st page header is enabled and disabled. So in this sample script, the headers and footers both with and without the check of the 1st page header are overwritten.

    Sample script:

    Please copy and paste the following script to the script editor, and set the values of templateDocumentId and folderId, and then, run main().

    function getObjs(dstDoc, key) {
      if (!dstDoc.getHeader()) dstDoc.addHeader();  // Added
      if (!dstDoc.getFooter()) dstDoc.addFooter();  // Added
    
      var dd = dstDoc.getHeader().getParent();
      var cc = dd.getNumChildren();
      const objs = [];
      for (let i = 0; i < cc; i++) {
        if (dd.getChild(i).getType() == DocumentApp.ElementType[key == "header" ? "HEADER_SECTION" : "FOOTER_SECTION"]) {
          objs.push(dd.getChild(i)[key == "header" ? "asHeaderSection" : "asFooterSection"]());
        }
      }
      return objs;
    }
    
    function copyFooter(tempDoc, dstDoc) {
      getObjs(dstDoc, "footer").forEach(dstFooter => {
        dstFooter.clear();
        const d = tempDoc.getFooter();
        const c = d.getNumChildren();
        for (let i = 0; i < c; i++) {
          const child = d.getChild(i);
          const type = child.getType();
          if (type == DocumentApp.ElementType.PARAGRAPH) {
            dstFooter.insertParagraph(i, child.copy().asParagraph());
          } if (type == DocumentApp.ElementType.TABLE) {
            dstFooter.insertTable(i, child.copy().asTable());
          }
        }
      });
    }
    
    function copyHeader(tempDoc, dstDoc) {
      getObjs(dstDoc, "header").forEach(dstHeader => {
        dstHeader.clear();
        const d = tempDoc.getHeader();
        const c = d.getNumChildren();
        for (let i = 0; i < c; i++) {
          const child = d.getChild(i);
          const type = child.getType();
          if (type == DocumentApp.ElementType.PARAGRAPH) {
            dstHeader.insertParagraph(i, child.copy().asParagraph());
          } if (type == DocumentApp.ElementType.TABLE) {
            const table = child.copy().asTable();
            let imgObj = [];
            for (let r = 0, rows = table.getNumRows(); r < rows; r++) {
              const row = table.getRow(r);
              for (let c = 0, cols = row.getNumCells(); c < cols; c++) {
                const cell = row.getCell(c);
                for (let ce = 0, cc = cell.getNumChildren(); ce < cc; ce++) {
                  if (cell.getChild(ce).getType() == DocumentApp.ElementType.PARAGRAPH) {
                    const cp = cell.getChild(ce).asParagraph();
                    for (let cee = 0, cpn = cp.getNumChildren(); cee < cpn; cee++) {
                      const ceec = cp.getChild(cee);
                      if (ceec.getType() == DocumentApp.ElementType.INLINE_IMAGE) {
                        const img = ceec.asInlineImage();
                        imgObj.push({child: cee, img: img, row: r, col: c, blob: img.getBlob(), width: img.getWidth(), height: img.getHeight()});
                        ceec.removeFromParent();
                      }
                    }
                  }
                }
    
              }
            }
            const dstTable = dstHeader.insertTable(i, table);
            if (imgObj.length > 0) {
              imgObj.forEach(({row, col, child, blob, width, height}) => dstTable.getCell(row, col).insertImage(child, blob).setWidth(width).setHeight(height));
            }
          }
        }
      });
    }
    
    // Please run this function.
    function main() {
      const templateDocumentId = "###";  // Please set the template Document ID.
      const folderId = "###";  // Please set the folder ID.
    
      const tempDoc = DocumentApp.openById(templateDocumentId);
      const docs = DriveApp.getFolderById(folderId).getFilesByType(MimeType.GOOGLE_DOCS);
      while (docs.hasNext()) {
        const docId = docs.next().getId();
        const dstDoc = DocumentApp.openById(docId);
        copyHeader(tempDoc, dstDoc);
        copyFooter(tempDoc, dstDoc);
      }
    }
    

    Note:

    • This sample script supposes that from your replying comments, although there are hundreds number of Google Document files you want to use, you will run the script for every several Documents. Please be careful this.
    • This sample script is used for your sample template Document. Because I have only the information from your sample template Document. When you change the structure of the template Document, this script might not be able to be used. Please be careful this.

    References: