Copying a section from Google Docs to another Doc using Apps Script - google-apps-script

I've successfully used this code to copy the entirety of one doc into another doc:
const newestFile = DocumentApp.openById("ID").getBody();
const archive = DocumentApp.openById("ID").getBody();
let index = 12;
let el, type;
for (let i = 0; i < newestFile.getNumChildren(); i++){
el = newestFile.getChild(i);
type = el.getType();
switch (type){
case DocumentApp.ElementType.PARAGRAPH:
archive.insertParagraph(index,el.copy());
index++;
break;
case DocumentApp.ElementType.LIST_ITEM:
archive.insertListItem(index,el.copy());
index++;
break;
case DocumentApp.ElementType.TABLE:
archive.insertTable(index,el.copy());
index++;
break;
}
}
However, I now need to copy a portion of a doc into another doc, and I can't figure it out. If I knew how to get the body index of any element I could do it the same way, but I don't know if that's even possible. The text I need to copy out will always be preceded by a specific text ("Current Week") and end immediatly before a specific text ("ARCHIVE").

Description
Here is a simple example of how to copy between certain text. I've only covered paragraphs and tables but any other type of Element can be handled.
Test Document
Script
function myFunction() {
try {
let doc = DocumentApp.getActiveDocument();
let body = doc.getBody();
let count = body.getNumChildren();
doc = DocumentApp.create("dummy");
let copy = doc.getBody();
let start = false;
for( let i=0; i<count; i++ ) {
let child = body.getChild(i);
if( child.getType() == DocumentApp.ElementType.PARAGRAPH ) {
if( child.asParagraph().findText("Current Week") ) start = true;
if( start ) copy.appendParagraph(child.asParagraph().copy());
if( child.asParagraph().findText("ARCHIVE") ) break;
}
else if( child.getType() == DocumentApp.ElementType.TABLE ) {
if( start ) copy.appendTable(child.asTable().copy());
}
}
}
catch(err) {
console.log("Error in myFunction - "+err)
}
}
Reference
https://developers.google.com/apps-script/reference/document/body#getChild(Integer)
https://developers.google.com/apps-script/reference/document/element-type
https://developers.google.com/apps-script/reference/document/body

Related

Get the first hyperlink and its text value

I hope everyone is in good health health and condition.
Recently, I have been working on Google Docs hyperlinks using app scripts and learning along the way. I was trying to get all hyperlink and edit them and for that I found an amazing code from this post. I have read the code multiple times and now I have a good understanding of how it works.
My confusion
My confusion is the recursive process happening in this code, although I am familiar with the concept of Recursive functions but when I try to modify to code to get only the first hyperlink from the document, I could not understand it how could I achieve that without breaking the recursive function.
Here is the code that I am trying ;
/**
* Get an array of all LinkUrls in the document. The function is
* recursive, and if no element is provided, it will default to
* the active document's Body element.
*
* #param {Element} element The document element to operate on.
* .
* #returns {Array} Array of objects, vis
* {element,
* startOffset,
* endOffsetInclusive,
* url}
*/
function getAllLinks(element) {
var links = [];
element = element || DocumentApp.getActiveDocument().getBody();
if (element.getType() === DocumentApp.ElementType.TEXT) {
var textObj = element.editAsText();
var text = element.getText();
var inUrl = false;
for (var ch=0; ch < text.length; ch++) {
var url = textObj.getLinkUrl(ch);
if (url != null) {
if (!inUrl) {
// We are now!
inUrl = true;
var curUrl = {};
curUrl.element = element;
curUrl.url = String( url ); // grab a copy
curUrl.startOffset = ch;
}
else {
curUrl.endOffsetInclusive = ch;
}
}
else {
if (inUrl) {
// Not any more, we're not.
inUrl = false;
links.push(curUrl); // add to links
curUrl = {};
}
}
}
if (inUrl) {
// in case the link ends on the same char that the element does
links.push(curUrl);
}
}
else {
var numChildren = element.getNumChildren();
for (var i=0; i<numChildren; i++) {
links = links.concat(getAllLinks(element.getChild(i)));
}
}
return links;
}
I tried adding
if (links.length > 0){
return links;
}
but it does not stop the function as it is recursive and it return back to its previous calls and continue running.
Here is the test document along with its script that I am working on.
https://docs.google.com/document/d/1eRvnR2NCdsO94C5nqly4nRXCttNziGhwgR99jElcJ_I/edit?usp=sharing
I hope you will understand what I am trying to convey, Thanks for giving a look at my post. Stay happy :D
I believe your goal as follows.
You want to retrieve the 1st link and the text of link from the shared Document using Google Apps Script.
You want to stop the recursive loop when the 1st element is retrieved.
Modification points:
I tried adding
if (links.length > 0){
return links;
}
but it does not stop the function as it is recursive and it return back to its previous calls and continue running.
About this, unfortunately, I couldn't understand where you put the script in your script. In this case, I think that it is required to stop the loop when links has the value. And also, it is required to also retrieve the text. So, how about modifying as follows? I modified 3 parts in your script.
Modified script:
function getAllLinks(element) {
var links = [];
element = element || DocumentApp.getActiveDocument().getBody();
if (element.getType() === DocumentApp.ElementType.TEXT) {
var textObj = element.editAsText();
var text = element.getText();
var inUrl = false;
for (var ch=0; ch < text.length; ch++) {
if (links.length > 0) break; // <--- Added
var url = textObj.getLinkUrl(ch);
if (url != null) {
if (!inUrl) {
// We are now!
inUrl = true;
var curUrl = {};
curUrl.element = element;
curUrl.url = String( url ); // grab a copy
curUrl.startOffset = ch;
}
else {
curUrl.endOffsetInclusive = ch;
}
}
else {
if (inUrl) {
// Not any more, we're not.
inUrl = false;
curUrl.text = text.slice(curUrl.startOffset, curUrl.endOffsetInclusive + 1); // <--- Added
links.push(curUrl); // add to links
curUrl = {};
}
}
}
if (inUrl) {
// in case the link ends on the same char that the element does
links.push(curUrl);
}
}
else {
var numChildren = element.getNumChildren();
for (var i=0; i<numChildren; i++) {
if (links.length > 0) { // <--- Added or if (links.length > 0) break;
return links;
}
links = links.concat(getAllLinks(element.getChild(i)));
}
}
return links;
}
In this case, I think that if (links.length > 0) {return links;} can be modified to if (links.length > 0) break;.
Note:
By the way, when Google Docs API is used, both the links and the text can be also retrieved by a simple script as follows. When you use this, please enable Google Docs API at Advanced Google services.
function myFunction() {
const doc = DocumentApp.getActiveDocument();
const res = Docs.Documents.get(doc.getId()).body.content.reduce((ar, {paragraph}) => {
if (paragraph && paragraph.elements) {
paragraph.elements.forEach(({textRun}) => {
if (textRun && textRun.textStyle && textRun.textStyle.link) {
ar.push({text: textRun.content, url: textRun.textStyle.link.url});
}
});
}
return ar;
}, []);
console.log(res) // You can retrieve 1st link and test by console.log(res[0]).
}

Can't transfer footnote into new document -- entire function fails

I'm writing a function that will duplicate the current document by moving every element into a new document -- it's part of a larger project.
However, when there is a footnote in the document, the function fails, with this error.
Service Documents failed while accessing document with id [id of target doc]
Now, I have no problem with the function not being able to transfer the footnote into a new document -- or at least transferring just the text of the footnote. However, this error stops my entire function from running (none of the elements in the doc get transferred over).
Is it possible to ignore footnotes or convert them to text, and allow the function to transfer all other elements into the new document?
Here's a sample document with a footnote
Here's my code:
function duplicateDocument() {
var currentDoc = DocumentApp.getActiveDocument().getBody();
var targetDoc = DocumentApp.create('New Doc');
var totalElements = currentDoc.getNumChildren();
//Goes through each type of element to preserve formatting
for( var index = 0; index < totalElements; ++index ) {
var body = targetDoc.getBody();
var element = currentDoc.getChild(index).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.BOOKMARK ){
body.appendBookmark(element);
} else if( type == DocumentApp.ElementType.INLINE_IMAGE ){
body.appendImage(element);
} else if( type == DocumentApp.ElementType.HORIZONTAL_RULE ){
body.appendHorizontalRule();
} else if( type == DocumentApp.ElementType.PAGE_BREAK ){
body.appendPageBreak();
}
}
}
At current stage it's unfortunately not possible to create footnotes programmatically with Apps Script
A related feature request already exists, but it has not been implemented yet
This must be the reason tht copying a paragraph with foot notes (so trying to create a footnote in the new document) results in buggy behavour
This issue seems also to be still in investigation
In the mean time - if we can live with copying the document without the footnotes - the best workaround would be to remove the footnotes programmatically
This you can do by looping through all the children of a paragraph, and if you encounter a footnote - remove it
Sample:
if( type == DocumentApp.ElementType.PARAGRAPH){
var num = element.asParagraph().getNumChildren();
for( var i = 0; i < num; i++ ) {
var child = element.asParagraph().getChild(i);
var childType = child.getType();
if( childType == DocumentApp.ElementType.FOOTNOTE){
child.removeFromParent();
}
}
body.appendParagraph(element);
}

Google Apps Script. Get all links from document

Hi all) I need to get all links from google document. I found that general approach:
function getAllLinks(element) {
var links = [];
element = element || DocumentApp.getActiveDocument().getBody();
if (element.getType() === DocumentApp.ElementType.TEXT) {
var textObj = element.editAsText();
var text = element.getText();
Logger.log("text " + text);
var inUrl = false;
for (var ch=0; ch < text.length; ch++) {
var url = textObj.getLinkUrl(ch);
if (url != null) {
if (!inUrl) {
// We are now!
inUrl = true;
var curUrl = {};
curUrl.element = element;
curUrl.url = String( url ); // grab a copy
curUrl.startOffset = ch;
}
else {
curUrl.endOffsetInclusive = ch;
}
}
else {
if (inUrl) {
// Not any more, we're not.
inUrl = false;
links.push(curUrl); // add to links
curUrl = {};
}
}
}
}
else {
var numChildren = element.getNumChildren();
for (var i=0; i<numChildren; i++) {
links = links.concat(getAllLinks(element.getChild(i)));
}
}
Logger.log(links);
}
It works perfectly fine if i, for example, type url in text, but if add link via menu ("Insert" -> "Link") it doesn't work, function getLinkUrl() returns null. Documentation contains info about Link class, i thought all links represented by it, but don't understand why i can't get link inserted via menu.
I thought maybe i can use some regular expression on text of document element, but if i add link via menu item i can specify custom label for link, which may not contain url in it.
Have anyone faced this scenario? What i missed?

How I can get the textwrap image in google doc

Due to this answer now I can make copy of elements with images right, but I spotted one more thing - if image inserted as 'in text' - then copying is done well. But when I make it 'text wrap', I can't find this element at all!
Here is the code of test:
function test_show_all_structure_of_doc() {
var final = 'final';
var doc = get_doc(working_directory, final);
var body = doc.getBody();
var elements = body.getNumChildren();
for( var i=0;i<elements;i++) {
var element = body.getChild(i).copy();
var type = element.getType();
if( type == DocumentApp.ElementType.PARAGRAPH ){
Logger.log('paragraph' + i);
var children = element.getNumChildren();
if (children >0) {
for (var j=0;j<children;j++) {
var subelement = element.getChild(j).copy();
var subtype = subelement.getType();
Logger.log('subelement ' + j + ":" + subtype);
if (subtype == DocumentApp.ElementType.TEXT) Logger.log(subelement.getText());
}
}
}
else if( type == DocumentApp.ElementType.TABLE ){
Logger.log('table');}
else if( type == DocumentApp.ElementType.LIST_ITEM ){
Logger.log('list item');}
else if( type == DocumentApp.ElementType.INLINE_IMAGE ){
Logger.log('inline image');}
else {
throw new Error("check what to do with this type of element : "+ type);
}
}
}
so where I can find textwrap image? Or it is impossible for now?
This is not possible using Google Apps Script (for now) and is the object of an enhancement request for more than 2 years now.
In the mean time, only inline image is supported.

Get child index of findtext in google API script

My goal is to replace a piece of text in a Google Drive document with the contents of another document.
I have been able to insert the document at a certain position in the other document, but I'm having trouble determining the child index of the piece of text I want to replace. Here is what I have so far:
function replace(docId, requirementsId) {
var body = DocumentApp.openById(docId).getActiveSection();
var searchResult = body.findText("<<requirementsBody>>");
var pos = searchResult.?? // Here I would need to determine the position of the searchResult, to use it in the insertParagraph function below
var otherBody = DocumentApp.openById(requirementsId).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.insertParagraph(pos,element);
} else if( type == DocumentApp.ElementType.TABLE ) {
body.insertTable(pos,element);
} else if( type == DocumentApp.ElementType.LIST_ITEM ) {
body.insertListItem(pos,element);
} else {
throw new Error("According to the doc this type couldn't appear in the body: "+type);
}
}
};
Any assistance would be greatly appreciated.
findText()
returns a RangeElement.
You can use
var r = rangeElement.getElement()
to get the element containing the found text.
To get its childIndex you can use
r.getParent().getChildIndex(r)
Thanks to bruce's answer I was able to figure out a solution to this problem, however if I was inserting Elements from another document I needed to actually find the index of the parent of the found text, as the found text was just a Text element inside of a Paragraph Element. So, I needed to find the index of the Paragraph Element, and then insert the new elements in relation to that Paragraph.
The code looks like this:
var foundTag = body.findText(searchPattern);
if (foundTag != null) {
var tagElement = foundTag.getElement();
var parent = tagElement.getParent();
var insertPoint = parent.getParent().getChildIndex(parent);
var otherBody = DocumentApp.openById(requirementsId).getActiveSection();
var totalElements = otherBody.getNumChildren();
for( var j = 0; j < totalElements; ++j ) {
... then same insertCode from the question above ...
insertPoint++;
}