/* Parsing from mangini/gdocs2md. Modified by clearf to add files to the google directory structure. Modified by lmmx to write Rmarkdown, with emphasis on chunks rather than HTML-rendering code. Usage: Adding this script to your doc: - Tools > Script Manager > New - Select "Blank Project", then paste this code in and save. Running the script: - Tools > Script Manager - Select "convertDocumentToRmarkdown" function. - Click Run button. - Converted doc will be added to a "Rmarkdown" folder in the source document's directories. - Images will be added to a subfolder of the "Rmarkdown" folder. */ function onInstall(e) { onOpen(e); } function onOpen() { // Add a menu with some items, some separators, and a sub-menu. setupScript(); // In future: // DocumentApp.getUi().createAddonMenu(); DocumentApp.getUi().createMenu('Rmarkdown') .addItem('Export \u2192 Rmd', 'convertSingleDoc') .addItem('Export folder \u2192 Rmd', 'convertFolder') .addToUi(); } function setupScript() { var scriptProperties = PropertiesService.getScriptProperties(); // manual way to do the following: // scriptProperties.setProperty("folder_id", "INSERT_FOLDER_ID_HERE"); // scriptProperties.setProperty("document_id", "INSERT_FILE_ID_HERE"); var doc_id = DocumentApp.getActiveDocument().getId(); scriptProperties.setProperty("document_id", doc_id); var doc_parents = DriveApp.getFileById(doc_id).getParents(); var folders = doc_parents; while (folders.hasNext()) { var folder = folders.next(); var folder_id = folder.getId(); } scriptProperties.setProperty("folder_id", folder_id); scriptProperties.setProperty("image_folder_prefix", "/images/"); } function getDocComments(comment_list_args) { if (typeof(comment_list_args) == 'undefined') { var comment_list_args = {}; } var possible_args = ['images', 'include_deleted']; for (var i in possible_args) { var possible_arg = possible_args[i]; if (comment_list_args.propertyIsEnumerable(possible_arg)) { eval(possible_arg + " = " + comment_list_args[possible_arg]); } else { eval(possible_arg + " = " + false); } } /* Looks bad but more sensible than repeatedly checking if arg undefined. Sets every variable named in the possible_args array to false if it wasn't passed into the comment_list_args object. */ var scriptProperties = PropertiesService.getScriptProperties(); var document_id = scriptProperties.getProperty("document_id"); var comments_list = Drive.Comments.list(document_id, {includeDeleted: include_deleted, maxResults: 100 }); // 0 to 100, default 20 // See https://developers.google.com/drive/v2/reference/comments/list for all options var comment_array = []; for (var i = 0; i < comments_list.items.length; i++) { var comment_text = comments_list.items[i].content; /* images is a generic parameter passed in as a switch to return image URL-containing comments only. If the parameter is provided, it's no longer undefined. */ if (images) { if (/(https?:\/\/.+?\.(png|gif|jpe?g))/.test(comment_text)) { comment_array.push(RegExp.$1); } // otherwise there's no image URL here, skip it } else { comment_array.push(comment_text); } } return comment_array; } function getImageComments() { // for testing/maybe easy shorthand getDocComments({images: true}); } function convertSingleDoc() { var scriptProperties = PropertiesService.getScriptProperties(); // renew comments list on every export var doc_comments = getDocComments(); var image_urls = getDocComments({images: true}); // NB assumed false - any value will do scriptProperties.setProperty("comments", doc_comments); scriptProperties.setProperty("image_srcs", image_urls); var folder_id = scriptProperties.getProperty("folder_id"); var document_id = scriptProperties.getProperty("document_id"); var source_folder = DriveApp.getFolderById(folder_id); var Rmarkdown_folders = source_folder.getFoldersByName("Rmarkdown"); var Rmarkdown_folder; if (Rmarkdown_folders.hasNext()) { Rmarkdown_folder = Rmarkdown_folders.next(); } else { // Create a Rmarkdown folder if it doesn't exist. Rmarkdown_folder = source_folder.createFolder("Rmarkdown") } convertDocumentToRmarkdown(DocumentApp.openById(document_id), Rmarkdown_folder); } function convertFolder() { var scriptProperties = PropertiesService.getScriptProperties(); var folder_id = scriptProperties.getProperty("folder_id"); var source_folder = DriveApp.getFolderById(folder_id); var Rmarkdown_folders = source_folder.getFoldersByName("Rmarkdown"); var Rmarkdown_folder; if (Rmarkdown_folders.hasNext()) { Rmarkdown_folder = Rmarkdown_folders.next(); } else { // Create a Rmarkdown folder if it doesn't exist. Rmarkdown_folder = source_folder.createFolder("Rmarkdown"); } // Only try to convert google docs files. var gdoc_files = source_folder.getFilesByType("application/vnd.google-apps.document"); // For every file in this directory while(gdoc_files.hasNext()) { var gdoc_file = gdoc_files.next() var filename = gdoc_file.getName(); var Rmd_files = Rmarkdown_folder.getFilesByName(filename + ".Rmd"); var update_file = false; if (Rmd_files.hasNext()) { var Rmd_file = Rmd_files.next(); if (Rmd_files.hasNext()){ // There are multiple Rmarkdown files; delete and rerun update_file = true; } else if (Rmd_file.getLastUpdated() < gdoc_file.getLastUpdated()) { update_file = true; } } else { // There is no folder and the conversion needs to be rerun update_file = true; } if (update_file) { convertDocumentToRmarkdown(DocumentApp.openById(gdoc_file.getId()), Rmarkdown_folder); } } } function convertDocumentToRmarkdown(document, destination_folder) { var scriptProperties = PropertiesService.getScriptProperties(); var image_prefix=scriptProperties.getProperty("image_folder_prefix"); var numChildren = document.getActiveSection().getNumChildren(); var text = ""; var Rmd_filename = document.getName()+".Rmd"; var image_foldername = document.getName()+"_images"; var inSrc = false; var inChunk = false; var globalImageCounter = 0; var globalListCounters = {}; // edbacher: added a variable for indent in src
block. Let style sheet do margin. var srcIndent = ""; var postHasImages = false; var files = []; // Walk through all the child elements of the doc. for (var i = 0; i < numChildren; i++) { var child = document.getActiveSection().getChild(i); var result = processParagraph(i, child, inSrc, inChunk, globalImageCounter, globalListCounters, image_prefix + image_foldername); globalImageCounter += (result && result.images) ? result.images.length : 0; if (result!==null) { if (result.sourcePretty==="start" && !inSrc) { inSrc=true; text+="\n"; } else if (result.sourcePretty==="end" && inSrc) { inSrc=false; text+="\n\n"; } else if (result.source==="start" && !inSrc) { inSrc=true; text+="\n"; } else if (result.source==="end" && inSrc) { inSrc=false; text+="\n\n"; } else if (result.inChunk==="start" && !inChunk) { inChunk=true; if (result.className==='') { text+="```{r}\n"; } else { text+="```{r "+result.className+"}\n"; } } else if (result.inChunk==="end" && inChunk) { inChunk=false; text+="```\n\n"; } else if (inChunk) { text+=result.text+"\n"; } else if (inSrc) { text+=(srcIndent+escapeHTML(result.text)+"\n"); } else if (result.text && result.text.length>0) { text+=result.text+"\n\n"; } if (result.images && result.images.length>0) { for (var j=0; j/g, '>'); } function standardQMarks(text) { return text.replace(/\u2018|\u8216|\u2019|\u8217/g,"'").replace(/\u201c|\u8220|\u201d|\u8221/g, '"') } // Process each child element (not just paragraphs). function processParagraph(index, element, inSrc, inChunk, imageCounter, listCounters, image_path) { // First, check for things that require no processing. if (element.getNumChildren()==0) { return null; } // Skip on TOC. if (element.getType() === DocumentApp.ElementType.TABLE_OF_CONTENTS) { return {"text": "[[TOC]]"}; } // Set up for real results. var result = {}; var pOut = ""; var textElements = []; var imagePrefix = "image_"; // Handle Table elements. Pretty simple-minded now, but works for simple tables. // Note that Rmarkdown does not process within block-level HTML, so it probably // doesn't make sense to add markup within tables. if (element.getType() === DocumentApp.ElementType.TABLE) { textElements.push("
" + element.getChild(i).getChild(j).getText() + " | \n"); } textElements.push("