Trying to create multiple PDF for each row in a Google Sheet - pdf

I am trying to create a separate PDF for each row in a Google Sheet using a Google Doc as a template. I can get it to work when I use the active row; however, when I try to loop the createpdf function the only PDF I am left with is the last row. My thoughts are that I am deleting all of the files beforehand but I can't figure out where the error is. Any help would be awesome.
var TEMPLATE_ID = '1Mxrm0kny8R7ROlBou8kSUc8dOvvH8PvtQ-EC_Nd3FUQ';
function onOpen() {
SpreadsheetApp
.getUi()
.createMenu('Create PDF')
.addItem('Create PDF', 'createPdf')
.addToUi();
}
for (var i=2; i < 3; i++) { function createPdf() {
if (TEMPLATE_ID === '') {
SpreadsheetApp.getUi().alert('TEMPLATE_ID needs to be defined in code.gs');
return;
}
// Set up the docs and the spreadsheet access
var copyFile = DriveApp.getFileById(TEMPLATE_ID).makeCopy(),
copyId = copyFile.getId(),
copyDoc = DocumentApp.openById(copyId),
copyBody = copyDoc.getBody(),
activeSheet = SpreadsheetApp.getActiveSheet(),
numberOfColumns = activeSheet.getLastColumn(),
activeRow = activeSheet.getRange(i, 1, 1, numberOfColumns).getValues(),
headerRow = activeSheet.getRange(1, 1, 1, numberOfColumns).getValues(),
columnIndex = 0,
fileName = activeRow[0][0] + activeRow[0][1],
pdfFile,
pdfFile2;
// Replace the keys with the spreadsheet values
for (;columnIndex < headerRow[0].length; columnIndex++) {
copyBody.replaceText('%' + headerRow[0][columnIndex] + '%',
activeRow[0][columnIndex]);
}
// Create the PDF file and delete the doc copy
copyDoc.saveAndClose();
pdfFile = DriveApp.createFile(copyFile.getAs("application/pdf"));
pdfFile2 = pdfFile.setName(fileName)
copyFile.setTrashed(true);
return pdfFile2;
}}

Tip: pay attention to formatting your code properly, because it will help you understand its structure. Here's what you have after passing it through jsbeautifier:
var TEMPLATE_ID = '1Mxrm0kny8R7ROlBou8kSUc8dOvvH8PvtQ-EC_Nd3FUQ';
function onOpen() {
SpreadsheetApp
.getUi()
.createMenu('Create PDF')
.addItem('Create PDF', 'createPdf')
.addToUi();
}
for (var i = 2; i < 3; i++) {
function createPdf() {
if (TEMPLATE_ID === '') {
SpreadsheetApp.getUi().alert('TEMPLATE_ID needs to be defined in code.gs');
return;
}
// Set up the docs and the spreadsheet access
var copyFile = DriveApp.getFileById(TEMPLATE_ID).makeCopy(),
copyId = copyFile.getId(),
copyDoc = DocumentApp.openById(copyId),
copyBody = copyDoc.getBody(),
activeSheet = SpreadsheetApp.getActiveSheet(),
numberOfColumns = activeSheet.getLastColumn(),
activeRow = activeSheet.getRange(i, 1, 1, numberOfColumns).getValues(),
headerRow = activeSheet.getRange(1, 1, 1, numberOfColumns).getValues(),
columnIndex = 0,
fileName = activeRow[0][0] + activeRow[0][1],
pdfFile,
pdfFile2;
// Replace the keys with the spreadsheet values
for (; columnIndex < headerRow[0].length; columnIndex++) {
copyBody.replaceText('%' + headerRow[0][columnIndex] + '%',
activeRow[0][columnIndex]);
}
// Create the PDF file and delete the doc copy
copyDoc.saveAndClose();
pdfFile = DriveApp.createFile(copyFile.getAs("application/pdf"));
pdfFile2 = pdfFile.setName(fileName)
copyFile.setTrashed(true);
return pdfFile2;
}
}
The indenting clarifies a few problems:
The for loop on i is in global scope, not part of any function. It will therefore execute every time ANY function in the script is invoked, but is not explicitly invocable, as it would be in a function.
The function createPDF is declared inside that for loop. This is in global scope (see point 1), so it's still available to be called from other functions such as the menu created by your onOpen(). HOWEVER, because it's just a declaration, it is NOT invoked by the i loop! (This was the primary concern of your question.)
There's a return call inside createPdf() that is probably a left-over from the original copy of this code which produced a single PDF. Because it returns in the first time through the loop, it will STILL make a single PDF. That line needs to be moved or deleted.
return pdfFile2;
Flipping those couple of lines and deleting the return should sort us out.
var TEMPLATE_ID = '1Mxrm0kny8R7ROlBou8kSUc8dOvvH8PvtQ-EC_Nd3FUQ';
function onOpen() {
SpreadsheetApp
.getUi()
.createMenu('Create PDF')
.addItem('Create PDF', 'createPdf')
.addToUi();
}
function createPdf() {
for (var i = 2; i < 3; i++) {
if (TEMPLATE_ID === '') {
SpreadsheetApp.getUi().alert('TEMPLATE_ID needs to be defined in code.gs');
return;
}
// Set up the docs and the spreadsheet access
var copyFile = DriveApp.getFileById(TEMPLATE_ID).makeCopy(),
copyId = copyFile.getId(),
copyDoc = DocumentApp.openById(copyId),
copyBody = copyDoc.getBody(),
activeSheet = SpreadsheetApp.getActiveSheet(),
numberOfColumns = activeSheet.getLastColumn(),
activeRow = activeSheet.getRange(i, 1, 1, numberOfColumns).getValues(),
headerRow = activeSheet.getRange(1, 1, 1, numberOfColumns).getValues(),
columnIndex = 0,
fileName = activeRow[0][0] + activeRow[0][1],
pdfFile,
pdfFile2;
// Replace the keys with the spreadsheet values
for (; columnIndex < headerRow[0].length; columnIndex++) {
copyBody.replaceText('%' + headerRow[0][columnIndex] + '%',
activeRow[0][columnIndex]);
}
// Create the PDF file and delete the doc copy
copyDoc.saveAndClose();
pdfFile = DriveApp.createFile(copyFile.getAs("application/pdf"));
pdfFile2 = pdfFile.setName(fileName)
copyFile.setTrashed(true);
}
}

Related

Same variable for multiple sheets but only execute if export button on one sheet is pressed

I am trying to code an data export. The export is executed when a button is pressed and the script linked to the button will be executed. The data is transfered to a masterdata sheet.
The script is valid for 4 different sheets and i am trying to code it, so that i do not have to rename all the vaiables for each sheet.
I found some solution that i tried, by creating a variable with the sheet names and then call this variable in the following code.
Problem 1: Error "TypeError: Cannot read property 'copyTo' of null" occurs.
Problem 2: How to ensure that only the data from the sheet where the export button is pressed will be exported?
I tried a lot and cannot find a solution.
var sheetListArray = ["B1-Prozessfragen", "B2-Prozessfragen"];
var ss = SpreadsheetApp.getActiveSpreadsheet();
for( var k = 1 ; k <= sheetListArray.length ; k++)
ExportData1g (sheetListArray[k]);
PB1g (sheetListArray[k]);
function ExportData1g (sheetname) {
var result = SpreadsheetApp.getUi().alert("Fertig-Hacken gesetzt?", SpreadsheetApp.getUi().ButtonSet.OK_CANCEL); {
if (result === SpreadsheetApp.getUi().Button.OK) {
const ssh=ss.getSheetByName(sheetname);
const cell = ssh.getRange("CheckButton1").getValue();
if (cell == "1" || "0") {
(PB1g ());
}
else {return;} }
else {return;}
}
const ssh=ss.getSheetByName(sheetname);
var dataRange = ssh.getRange('CheckBox1');
var values = dataRange.getValues();
for (var i = 0; i < values.length; i++) {
for (var j = 0; j < values[i].length; j++) {
if (values[i][j] == true) {
values[i][j] = false;
}}
dataRange.setValues(values);
var commentRange = ssh.getRange ('ResetComment1');
var dataRange = ssh.getRange('EmptyBoxes1');
var values = dataRange.getValues();
for (var i = 0; i < values.length; i++) {
for (var j = 0; j < values[i].length; j++) {
if (values[i][j] == 1) {
values[i][j] = 0;
}
}
}
commentRange.clearContent();
dataRange.setValues(values);
} }
function PB1g(sheetname) {
const ssh=ss.getSheetByName(sheetname);
const tss=SpreadsheetApp.openById("1wMw5H48Fmc1R8WGy34d80x9-W996c9GNVuAD3-mFtVQ");
const tsh=tss.getSheetByName("PB");
const nsh=ssh.copyTo(tss);
var firstEmtyRow = tsh.getLastRow () + 1;
var timestamp = new Date();
tsh.appendRow([timestamp]);
nsh.getRange("CopytoMasterdata1").copyTo(tsh.getRange("B" + firstEmtyRow), {contentsOnly:true});
tss.deleteSheet(nsh); }

How to parse subfolders for find specific files?

I Would like to create a script for photoshop who allow me to search some files with a specific name (for example : 300x250_F1.jpg, 300x250_F2.jpg, 300x600_F1.jpg, etc... ) in differents subfolders (all in the same parent folder) and after load them in my active document. The problem is names of subfolders will be everytime differents.
I definitely need some help :)
i found a code which almost do what i want (thank you).
I'm almost good but i have a problem: if the variable "mask" have only one value, it works. But with few values, it doesn't work anymore.
I think it's because i made an array with the mask variable and i have to update the script...
var topFolder = Folder.selectDialog("");
//var topFolder = new Folder('~/Desktop/PS_TEST');
var fileandfolderAr = scanSubFolders(topFolder, /\.(jpg)$/i);
//var fileandfolderAr = scanSubFolders(topFolder, /\.(jpg|tif|psd|bmp|gif|png|)$/i);
var fileList = fileandfolderAr[0];
var nom = decodeURI(fileList);
//all file paths found and amount of files found
//alert("fileList: " + nom + "\n\nFile Amount: " + fileList.length);
//alert(allFiles);
for (var a = 0; a < fileList.length; a++) {
var docRef = open(fileList[a]);
//do things here
}
function scanSubFolders(tFolder, mask) { // folder object, RegExp or string
var sFolders = [];
var allFiles = [];
var mask = ["300x250_F1", "300x250_F2"];
sFolders[0] = tFolder;
for (var j = 0; j < sFolders.length; j++) { // loop through folders
var procFiles = sFolders[j].getFiles();
for (var i = 0; i < procFiles.length; i++) { // loop through this folder contents
if (procFiles[i] instanceof File) {
if (mask == undefined) {
allFiles.push(procFiles); // if no search mask collect all files
}
if (procFiles[i].fullName.search(mask) != -1) {
allFiles.push(procFiles[i]); // otherwise only those that match mask
}
}
else if (procFiles[i] instanceof Folder) {
sFolders.push(procFiles[i]); // store the subfolder
scanSubFolders(procFiles[i], mask); // search the subfolder
}
}
}
return [allFiles, sFolders];
}
There are several ways you can accomplish this without reassigning mask within the scanSubFolders function.
Solution 1: use a regex
The function is already set up to accept a regex or string as a mask. You just need to use one that would match the pattern of the files you're targeting.
var fileandfolderAr = scanSubFolders(topFolder, /300x250_F(1|2)/gi);
Solution 2: call the function within a loop
If regex isn't your thing, you could still utilize an array of strings, but do it outside the function. Loop the array of masks and call the function with each one, then execute your primary logic on the results of each call.
var topFolder = Folder.selectDialog("");
var myMasks = ["300x250_F1", "300x250_F2"];
for (var index in myMasks) {
var mask = myMasks[index]
var fileandfolderAr = scanSubFolders(topFolder, mask);
var fileList = fileandfolderAr[0];
for (var a = 0; a < fileList.length; a++) {
var docRef = open(fileList[a]);
//do things here
}
}
Don't forget to remove var mask = ["300x250_F1", "300x250_F2"]; from within the scanSubFolders function or else these won't work.

Run last Photoshop script (again)

This seems like a trivial issue but I'm not sure Photoshop supports this type of functionality:
Is it possible to implement use last script functionality?
That is without having to add a function on each and every script that writes it's filename to a text file.
Well... It's a bit klunky, but I suppose you could read in the scriptlistener in reverse order and find the first mention of a script file:
// Switch off any dialog boxes
displayDialogs = DialogModes.NO; // OFF
var scripts_folder = "D:\\PS_scripts";
var js = "C:\\Users\\GhoulFool\\Desktop\\ScriptingListenerJS.log";
var jsLog = read_file(js);
var lastScript = process_file(jsLog);
// use function to call scripts
callScript(lastScript)
// Set Display Dialogs back to normal
displayDialogs = DialogModes.ALL; // NORMAL
function callScript (ascript)
{
eval('//#include "' + ascript + '";\r');
}
function process_file(afile)
{
var needle = ".jsx";
var msg = "";
// Let's do this backwards
for (var i = afile.length-1; i>=0; i--)
{
var str = afile[i];
if(str.indexOf(needle) > 0)
{
var regEx = str.replace(/(.+new\sFile\(\s")(.+\.jsx)(.+)/gim, "$2");
if (regEx != null)
{
return regEx;
}
}
}
}
function read_file(inFile)
{
var theFile = new File(inFile);
//read in file
var lines = new Array();
var l = 0;
var txtFile = new File(theFile);
txtFile.open('r');
var str = "";
while(!txtFile.eof)
{
var line = txtFile.readln();
if (line != null && line.length >0)
{
lines[l++] = line;
}
}
txtFile.close();
return lines;
}

NullReference Error while reading xlsx EPPlus Core

I'm trying to upload an excel file with EPPlus. I'ts like my excel file was empty, but it has content inside.
Im getting this error:
worksheet.Dimension.Rows = 'worksheet.Dimension.Rows' threw an exception of type 'System.NullReferenceException'
on this line: int rowCount = worksheet.Dimension.Rows;
[HttpPost("Excel")]
public string UploadExcel(IFormFile file)
{
var filePath = Path.GetTempFileName();
FileInfo excel = new FileInfo(filePath);
using (ExcelPackage package = new ExcelPackage(excel))
{
StringBuilder sb = new StringBuilder();
ExcelWorksheet worksheet = package.Workbook.Worksheets[file.FileName];
int rowCount = worksheet.Dimension.Rows;
int ColCount = worksheet.Dimension.Columns;
bool bHeaderRow = true;
for (int row = 1; row <= rowCount; row++)
{
for (int col = 1; col <= ColCount; col++)
{
if (bHeaderRow)
{
sb.Append(worksheet.Cells[row, col].Value.ToString() + "\t");
}
else
{
sb.Append(worksheet.Cells[row, col].Value.ToString() + "\t");
}
}
sb.Append(Environment.NewLine);
}
return sb.ToString();
}
Can you verify that the workshseets are being loaded?
//examine in the debugger to make sure the worksheet you want is loaded
var worksheets = package.Workbook.Worksheets.ToList();
If that doesn't work, can you try one of these?
var worksheet = package.Workbook.Worksheets[0];
var worksheetFirst = package.Workbook.Worksheets.First();
UPDATE:
The real error is that you are trying to read a temp file with var filePath = Path.GetTempFileName();. You need to read an actual excel file. For example, you could use Path.GetFullPath("path/to/the/excel/file.xls");

Replacing smart objects in bulk with Photoshop

Just facing this issue: I have a mockup in Photoshop with two smart-objects: Rectangle 14.psb and Place your logo.psb
I have 100+ images in png that should be applied to create mockups.
For this reason, I would like your help to create a script that:
Let me select the png file that I would like to use
Open the smart objects (Rectangle 14.psb and Place your logo.psb)
Re-Link the same png to the layers "place your logo" of both the smart objects.
Finally, the script should save the file as png with the same file name of the selected png file adding just _new after its name.
So far I have tried this code without any luck:
#target photoshop
if (app.documents.length > 0) {
var myDocument = app.activeDocument;
var theName = myDocument.name.match(/(.*)\.[^\.]+$/)[1];
var thePath = myDocument.path;
var theLayer = myDocument.activeLayer;
// PSD Options;
psdOpts = new PhotoshopSaveOptions();
psdOpts.embedColorProfile = true;
psdOpts.alphaChannels = true;
psdOpts.layers = true;
psdOpts.spotColors = true;
// Check if layer is SmartObject;
if (theLayer.kind != "LayerKind.SMARTOBJECT") {
alert("selected layer is not a smart object")
} else {
// Select Files;
if ($.os.search(/windows/i) != -1) {
var theFiles = File.openDialog("please select files",
"*.psd;*.tif;*.jpg;*.png", true)
} else {
var theFiles = File.openDialog("please select files", getFiles,
true)
};
if (theFiles) {
for (var m = 0; m < theFiles.length; m++) {
// Replace SmartObject
theLayer = replaceContents(theFiles[m], theLayer);
var theNewName = theFiles[m].name.match(/(.*)\.[^\.]+$/)[1];
// Save JPG
myDocument.saveAs((new File(thePath + "/" + theName + "_" +
theNewName + ".psd")), psdOpts, true);
}
}
}
};
// Get PSDs, TIFs and JPGs from files
function getFiles(theFile) {
if (theFile.name.match(/\.(psd|png|jpg)$/i) != null ||
theFile.constructor.name == "Folder") {
return true
};
};
// Replace SmartObject Contents
function replaceContents(newFile, theSO) {
app.activeDocument.activeLayer = theSO;
// =======================================================
var idplacedLayerReplaceContents =
stringIDToTypeID("placedLayerReplaceContents");
var desc3 = new ActionDescriptor();
var idnull = charIDToTypeID("null");
desc3.putPath(idnull, new File(newFile));
var idPgNm = charIDToTypeID("PgNm");
desc3.putInteger(idPgNm, 1);
executeAction(idplacedLayerReplaceContents, desc3, DialogModes.NO);
return app.activeDocument.activeLayer
};
The above code substitute the smart object but I would like just to re-link the layer withing the smartobject to a new image and save the file. Any help would be much appreciated!
Are you familiar with Scriptlistener? You can use it to get all the functions you need and then modify the output to run within your loop of 100 pngs, it should be straightforward.