Get data directly from Excel
Copy link to clipboard
Copied
Hi all,
Quite often I have to write scripts that use data originated from an Excel worksheet. I (and most scripters) believed that the easiest approach was to use a CSV-file exported from Excel. However, this takes an extra step for the user so I decided to write a function that opens an Excel book in background, reads the data from its first spreadsheet and returns array.
The function has three arguments:
- excelFilePath — The platform-specific full path name for the xlsx-file — fsName. If you pass it as a string, make sure to double the backslashes in the path like in the line below:
var excelFilePath = "D:\\My Test Folder\\SampleBook.xlsx";
- splitChar — [Optional] the character to use for splitting the columns in the spreadsheed: e.g. semicolon (;) or tab (\t)
If it isn't set, semicolon will be used by default. - sheetNumber — [Optional] the worksheet number: either String or Number. If it isn't set, the first worksheet will be used by default
The data in Excel
The same data transfered to InDesign as Array
The function (current version):
Main();
function Main() {
// The platform-specific full path name for the xlsx-file -- fsName
// If you pass it as a string, make sure to double the backslashes in the path like in the line below
var excelFilePath = "D:\\My Test Folder\\SampleBook.xlsx";
// [Optional] the character to use for splitting the columns in the spreadsheed: e.g. semicolon (;) or tab (\t)
// If it isn't set, semicolon will be used by default
var splitChar = ";";
// [Optional] the worksheet number: either string or number. If it isn't set, the first worksheet will be used by default
sheetNumber = "1";
var data = GetDataFromExcelPC(excelFilePath, splitChar, sheetNumber); // returns array
}
function GetDataFromExcelPC(excelFilePath, splitChar, sheetNumber) {
if (typeof splitChar === "undefined") var splitChar = ";";
if (typeof sheetNumber === "undefined") var sheetNumber = "1";
var vbs = 'Public s, excelFilePath\r';
vbs += 'Function ReadFromExcel()\r';
vbs += 'Set objExcel = CreateObject("Excel.Application")\r';
vbs += 'Set objBook = objExcel.Workbooks.Open("' + excelFilePath + '")\r';
vbs += 'Set objSheet = objExcel.ActiveWorkbook.WorkSheets(' + sheetNumber + ')\r';
vbs += 'objExcel.Visible = False\r';
vbs += 'matrix = objSheet.UsedRange\r';
vbs += 'maxDim0 = UBound(matrix, 1)\r';
vbs += 'maxDim1 = UBound(matrix, 2)\r';
vbs += 'For i = 1 To maxDim0\r';
vbs += 'For j = 1 To maxDim1\r';
vbs += 'If j = maxDim1 Then\r';
vbs += 's = s & matrix(i, j)\r';
vbs += 'Else\r';
vbs += 's = s & matrix(i, j) & "' + splitChar + '"\r';
vbs += 'End If\r';
vbs += 'Next\r';
vbs += 's = s & vbCr\r';
vbs += 'Next\r';
vbs += 'objBook.close\r';
vbs += 'Set objBook = Nothing\r';
vbs += 'Set objExcel = Nothing\r';
vbs += 'End Function\r';
vbs += 'Function SetArgValue()\r';
vbs += 'Set objInDesign = CreateObject("InDesign.Application")\r';
vbs += 'objInDesign.ScriptArgs.SetValue "excelData", s\r';
vbs += 'End Function\r';
vbs += 'ReadFromExcel()\r';
vbs += 'SetArgValue()\r';
app.doScript(vbs, ScriptLanguage.VISUAL_BASIC, undefined, UndoModes.FAST_ENTIRE_SCRIPT);
var str = app.scriptArgs.getValue("excelData");
app.scriptArgs.clear();
var tempArrLine, line,
data = [],
tempArrData = str.split("\r");
for (var i = 0; i < tempArrData.length; i++) {
line = tempArrData;
if (line == "") continue;
tempArrLine = line.split(splitChar);
data.push(tempArrLine);
}
return data;
}
I wrote and tested it in InDesign CC 2017 and Excel 2016 (Version 16) on Windows 10. I wonder if/how it works in other versions of Excel. Also, I'd like to get some feedback: bug reports, suggestions and new ideas.
Here's the permanent link on my site.
— Kas
Copy link to clipboard
Copied
Sorry to hear about the illness. I’m surely not helping. So, I’ve been using the Mac version with near success, but like I said no column or row separators and the file name doesn’t seem to work, though the script gets the data from whichever excel workbook is open. Get better soon! Maybe you can revisit my issue when you’re better. Thanks!
Copy link to clipboard
Copied
Hi pjleveno ,
just tested Kasyan's version 2 script with an Excel file on Windows 10.
The function returns data, that is an array of arrays. Perhaps that was not clear.
So you cannot directly use it as contents for an InDesign table.
Here a representation of the data of an Excel file with only one worksheet and a data representation like that:
The array's length is 5. Every entry represents a row in the worksheet.
So every entry of the 5 entries of the outer array is an array of 3 elements like that:
1,1,1
2,2,2
3,3,3
4,4,4
5,5,5
You could flatten the array of arrays to one array of string entries. Only then you could assign this array as contents for a table. In my reply above I was under the false impression that Kasyan's function returns an flat array. Totally my fault, because Kasyan made that very clear in his initial post.
Or, you do not flatten the result array and loop through the outer array to assign the inner arrays with the 3 entries as contents for rows of a table in InDesign.
Below a sample snippet where I pointed the script to an Excel file on drive F: and directed the entries of the result data as contents to the rows of an InDesign table in my open InDesign document:
function Main() {
// The platform-specific full path name for the xlsx-file -- fsName
// If you pass it as a string, make sure to double the backslashes in the path like in the line below
var excelFilePath = "F:\\Excel-TestFolder\\Columns-3-Cells-5.xlsx";
// [Optional] the character to use for splitting the columns in the spreadsheed: e.g. semicolon (;) or tab (\t)
// If it isn't set, semicolon will be used by default
var splitChar = ";";
// [Optional] the worksheet number: either string or number. If it isn't set, the first worksheet will be used by default
var sheetNumber = "1";
var data = GetDataFromExcelPC(excelFilePath, splitChar, sheetNumber); // returns array of arrays
//~ $.writeln( data.constructor.name );
//~ $.writeln( "data.length" +" : "+ data.length );
//~
//~ $.writeln(data.join("\r"));
//~
var myInDesignTable = app.activeDocument.textFrames[0].tables[0];
for( var n=0;n<data.length;n++ )
{
myInDesignTable.rows
.contents = data ; };
}
Screenshot of the result:
Sorry. Couldn't test the Mac OSX version code.
All my tests with Excel 2010 and InDesign CC 2019 version 14.0.1.209 on Windows 10 Pro.
Regards,
Uwe
//EDITED
Copy link to clipboard
Copied
Uwe,
Thanks! That's a relief that it was a particular problem of the Mac. So I have to use this. I was just doing a similar thing with xml, trying to point the contents from excel to cells in indd. It worked, though I do everything the slowest more laborious way possible. Thanks for helping yet again. I'll try your snippet and see if I can get the same result.
Paul
Copy link to clipboard
Copied
YOU NAILED IT! That is precisely what I wanted. Thanks to both of you for caring so much. I truly am grateful. Goodbye place(File)! forever.
Paul
Copy link to clipboard
Copied
Wow, all that work, and I just took the contents from the same workbook that originally prompted me to seek an alternative to place, and when I “get data” using your script, Kasyan, indd crashed, but only on that sheet, just like it was doing before. Are there characters in the text that could be causing this crash? The content includes equations because it refers to a math textbook. If your script can’t place it, and indd place can’t place it, well, that’s all there is.
Paul
Copy link to clipboard
Copied
pjleveno wrote
…The content includes equations because it refers to a math textbook. If your script can’t place it, and indd place can’t place it, well, that’s all there is.
Hi Paul,
before placing you could "flatten" your Excel file and remove all equations, but keeping the results:
Delete or remove a formula - Excel
Regards,
Uwe
Copy link to clipboard
Copied
Thanks again for taking an interest. I am now rolling and with your help, this looks great. I still haven’t figured out how to pass a file into the function properly. When I simply write it out with colons it works, but when I try to pass the file in as a variable, it doesn’t work. I’m on a Mac. Is there something I have to do to the file variable so that it works? Really, this is way beyond what I thought I was going to get out of this script. It has saved me! Thanks!
Paul
Copy link to clipboard
Copied
I still haven’t figured out how to pass a file into the function properly. When I simply write it out with colons it works, but when I try to pass the file in as a variable, it doesn’t work. I’m on a Mac.
I can't get to my Mac now, but here a couple of examples:
Here Macintosh HD is a local hard drive
var excelFilePath = "Macintosh HD:Ecclesall Print:Christmas card project:Schools:Ecclesall Junior School:Ecclesall Junior School.xlsx";
Here Test is a network share
var excelFilePath = "Test:My Folder:SampleBook.xlsx";
Here's a snippet from a script of mine (the beginning of a function) to give you idea how to 'convert' JS-path.
function NormalizeData(schoolFolder) {
var spread, classSpread, row,
book = [],
splitChar = ";";
var excelFiles = schoolFolder.getFiles("*.xlsx");
if (excelFiles.length == 0) {
return null;
}
var excelFile = excelFiles[0]; // get the 1st excel file: the name doesn't matter
if (File.fs == "Windows") {
var excelFilePath = excelFile.fsName.replace(/\\/g, "\\\\");
}
else {
var excelFilePath = excelFile.fullName.replace(/^\//g, "").replace(/\//g, ":");
}
// Get data from Excel
try {
if (File.fs == "Windows") {
var firstSpread = GetDataFromExcelPC(excelFilePath, splitChar, 1);
}
else {
var firstSpread = GetDataFromExcelMac(excelFilePath, "|", splitChar, 1);
}
By the way, my script works with a workbook who has formulas. My client sends to customers a blank copy of Excel file and they fill in the order: names, how many of which products to print, etc. Excel calculates the cost on the fly and the file is used to create the required products: PDF-files ready to print. Everything is processed automatically.
— Kas
Copy link to clipboard
Copied
Kasyan,
Perfect! I had to add replace(/^~/g, "Macintosh HD:Users:me”)
But that was the move. Your script is fantastic. I hope to learn how to use it in other cases. I can imagine many great uses. Absolutely perfect for the case I’m on now, but surely if I ever deal with excel in the future, I’ll be using this. Not having to declare range names which required save as xls, has saved me all sorts of problems. Now I can leave you in peace!
Paul
Copy link to clipboard
Copied
I had to add replace(/^~/g, "Macintosh HD:Users:me”)
I am glad to hear you finally sorted it out on your own. Though I don't understand why would you want to globally replace a tilda at the beginning of the string with a Mac specific path.
— Kas
Copy link to clipboard
Copied
Kasyan,
I don’t suppose the “g” is necessary. I’m just doing trial and error. Of course the worst way.
For example, I was using a pipe as row separator and semicolon as column separator, and everything was fine except when there is a semicolon in the content of a cell on the table I’m trying to place. Can I put anything I like as a column separator to avoid confusion? Could column and row separator character be multiple characters? I replaced the semicolon with a dollar sign and it worked…until the text is about dollars, I’m sure.
Thanks again!
Copy link to clipboard
Copied
Kasyan,
Answered my own question. So, unless you tell me otherwise, it seems like there's no downside to creating column and row separators that are very unlikely to appear in the content. On a side note, I was exploring your "File" scripts. Very exciting!
Paul
Copy link to clipboard
Copied
it seems like there's no downside to creating column and row separators that are very unlikely to appear in the content.
That’s right. I am sure, in most cases, the default separators -- pile and semicolon -- would work without any issues. If they are used somewhere in text, you can use some other chars instead.
— Kas
Copy link to clipboard
Copied
I have noticed that some characters script read as code instead actual character, for example:
right single quotation mark - \u2019
left double quotation mark - \u201C
right double quotation mark - \u201D
How we can change code to give actual character instead of code?
Copy link to clipboard
Copied
I just tested it (Windows 10, InDesign 14.0.2, Excel 2019 (16.0.11727.20222)), but this doesn't happen on my side.
Spreadsheet
Array in data browser
The same array converted to string and placed as contents into a text frame by script
I have a superficial knowledge of Excel and have no idea why it works differently for you. Probably you have to play with some settings.
— Kas
Copy link to clipboard
Copied
Probably i will try to solve this with native js function decodeURIComponent().
Thank you for this code.
Copy link to clipboard
Copied
Probably you won't need it.
For example (assuming a text frame is selected):
main();
function main() {
var textFrame = app.selection[0];
var str = "left single quotation mark - \u2018\nright single quotation mark - \u2019\nleft double quotation mark - \u201C\nright double quotation mark - \u201D";
textFrame.contents = str;
}
I get quotes as expected.
Also, in my opinion, these encode/decode functions have a different purpose: encode/decode special chars in URI.
Anyway, taking pairs of single and double quotation marks as an example, in ESTK they work like so:
Not what you expect to get (I guess).
Copy link to clipboard
Copied
Uwe, thanks, the penny finally dropped, TableFormattingOptions.EXCEL_FORMATTED_TABLE did the trick with my -1 problem.
Loic, I am going to look into a C++ solution.
P.
Copy link to clipboard
Copied
Pickory wrote
Uwe, thanks, the penny finally dropped, TableFormattingOptions.EXCEL_FORMATTED_TABLE did the trick with my -1 problem …
Hi Pickory,
aha!
I guess it is all cells formated as text with Excel and place unformatted,
e.g. as EXCEL_UNFORMATTED_TABBED_TEXT
vs.
using EXCEL_FORMATTED_TABLE with the options
if the cells are not formatted as text.
Regards,
Uwe
Copy link to clipboard
Copied
What a coincidence, I was just about to share my little script that also touches the topic of using Excel data without going the long route through a txt file. Sometimes, I only need some small table data copied over from Excel into my library or selected ID table. I came up with the idea to copy some cells from Excel into the clipboard and then just run the script. It places the clipboard into a temp frame and moves it into an array.
Notes:
- Replaced the typographer's apostrophe before moving it to the array, so that the typo settings don't matter. Apostrophes can cause a problem when the data is supposed to link to an image later.
- Not sure if there is a more elegant solution to get rid of the paragraph mark
I have been using it quite a bit and it works flawless. Any suggestions how to improve it? One from me: The new text frame is always placed at 0,0 so when I'm zoomed in on the righthand side page, the script jumps to that 0,0 location, which is a bit annoying. Could create the frame next to a selected object or active page.
function ClipboardToArray() {
var myFrame = app.activeWindow.activePage.textFrames.add(undefined, undefined, undefined, {geometricBounds:[0,0,100,100]}),
myArray = [];
with (myFrame.textFramePreferences) {
autoSizingType = AutoSizingTypeEnum.HEIGHT_AND_WIDTH;
autoSizingReferencePoint = AutoSizingReferenceEnum.TOP_LEFT_POINT;
useNoLineBreaksForAutoSizing = true;
}
myFrame.insertionPoints[0].select(); app.paste();
app.findGrepPreferences = app.changeGrepPreferences = null;
app.findGrepPreferences.findWhat = "'";
app.changeGrepPreferences.changeTo = "~'";
myFrame.changeGrep();
for (var i = 0; i < myFrame.paragraphs.length; i++) {
var myLine = myFrame.paragraphs.contents;
if (i < myFrame.paragraphs.length -1) {
myLine = myLine.substring(0, myLine.length -1);
}
myLine = myLine.split("\t");
myArray.push(myLine);
}
myFrame.remove();
return myArray;
}
Copy link to clipboard
Copied
Oh, and sorry for my ignorance, how do you get the nice background when entering code? Just <code> in HTML mode?
Copy link to clipboard
Copied
AnotherFrank wrote
Oh, and sorry for my ignorance, how do you get the nice background when entering code? Just <code> in HTML mode?
Hi Frank,
log in to the forum and click:
Use advanced editor
Some new formatting features should be available now.
Select your text you want to format as code.
Go to >> Syntax Highlighting > javascript
( you have to scroll a bit down to see this option )
Unfortunately enable Use advanced editor is no personal preference with the forum software so you have to click it every time you reply.
Important: With Inbox, Use advanced editor is not available.
Regards,
Uwe
Copy link to clipboard
Copied
Thanks, Uwe!
Copy link to clipboard
Copied
There might be dll/framework solutions for sure. I have been told one but it's a commercial one and it's like 1.3k € to distribute it. So it's quite expensive if it's for selling a script 500€
It seems python has some utilities too but it implies python being installed.
I wish I knew C++
Loic
Copy link to clipboard
Copied
Thanks! I feel like I'm so close to understanding, yet I don't see how I get "data" outside the function. What am I doing wrong?
Main();
app.activeDocument.textFrames[0].tables[0].contents=data;
function Main() {
returns "data is undefined"
How do I refer to "data" outside the function? I am sure I am missing some basic javascripting skills, so please be patient with me. You guys are great, by the way. I try to learn from your scripts all the time. Thanks again!
Paul

