Skip to main content
Kasyan Servetsky
Legend
February 16, 2017
Question

Get data directly from Excel

  • February 16, 2017
  • 8 replies
  • 18101 views

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:

  1. 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";
  2. 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.
  3. 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

This topic has been closed for replies.

8 replies

Participating Frequently
February 18, 2019

Dear Kasyan,

Everything works perfectly, and has been for a while, but I noticed that I am losing text formatting when I get data from Excel. I have tried adjusting the settings in clipboard handling and I am still losing the bold and italics. Is there a setting that I can adjust or a modification I can make to the script to preserve formatting? Sorry to reappear. I had hoped to leave you in peace. Thanks for all the help yet again.

Paul

Participating Frequently
February 19, 2019

I have been trying to come up with a solution. Do you think finding all italicized and bold text in excel and tagging it so that I can find and replace with style in indesign is a good approach? It's all I could come up with.

Paul

Kasyan Servetsky
Legend
February 19, 2019

Hi Paul,

Are you changing text using the contents property?

If so, try to use find-change Text/GREP instead.

— Kas

Participating Frequently
January 31, 2019

And, last of all:

I bet I'll have to modify this in NormalizeData:

var excelFilePath = excelFile.fsName.replace(/\\/g, "\\\\");

because I'm on a Mac. Sorry for the lameness.

Participating Frequently
January 31, 2019

Ok, I "figured it out".

var result= GetDataFromExcelMac("myFolder:Desktop: SampleBook.xlsx", "|", ";", 2);

but then I tried table.contents=result

and got "expected array of strings... but received... followed by a list of the contents of the table in excel set in parentheses and separated by commas.

Getting closer, at least.

Participating Frequently
January 31, 2019

One last thing: on a Mac, how do I have to express the file name? I tried many combinations last night, none worked. However, getDataFromExcelMac worked on the open excel doc.

I tried to follow this indication

// If you pass it as a string, make sure to double the backslashes in the path like in the line below

  var excelFilePath = "Macintosh HD:Users:me:Desktop:SampleBook.xlsx";

I do not find an example of doubled backslashes below. Again, thanks for your patience.

Paul

Participating Frequently
January 31, 2019

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

Loic.Aigon
Legend
February 20, 2017

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

Inspiring
February 19, 2017

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;

}

Inspiring
February 19, 2017

Oh, and sorry for my ignorance, how do you get the nice background when entering code? Just <code> in HTML mode?

Community Expert
February 20, 2017

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

Community Expert
February 16, 2017

Hi Kas and Loic,

yeah, funny. I'm also involved in a XLSX based project right now.

Kasyan asked for ideas:

What I'm doing is to place the XSLX file with an InDesign document and read out the data from there with table.contents.

That could return a String object (one cell in the table) or an Array of Strings (more cells than one in the table).

Advantage:

No need to have Excel installed.

Disadvantage:

Sometimes the import filter does not return a table object after placing, but a simple text frame.
I already saw this problem with InDesign CS4 using the UI's place command. So I always have to check for a Table object. If there is none I will convert the text—if there is any text—to table.

Other obstacles:

If the Excel file resides on the user's computer everything is working as expected.

But if the user is connected via VPN and the Excel file resides somewhere on the network, InDesign's place command ( by scripting, not via the UI ) will return nothing reasonable. If the user is placing the Excel file via UI all the data flows to a page. That's a bit odd…
At least a check on the returned contents where the placing is done by scripting suggests that the Excel file written is empty.

Currently I cannot test with the customer's computer to tackle this problem.

The customer is on Windows 10 I think and is using InDesign CC 2017.

Maybe I first have to duplicate the Excel file from the network to the local machine to proceed to tackle the VPN problem?

Waiting until a EOF is reached? Then Loic's approach could be the better solution. Have to dig into that…

Regards,
Uwe

Kasyan Servetsky
Legend
February 16, 2017

Thanks Loic and Uwe for your prompt replies! 

Uwe, I used the approach you suggested a few days ago in this post.

—Kas

Community Expert
February 16, 2017

Ah. Thanks.

Forgot to mention that I use page.place() to get the Excel data in. You are suggesting textFrame.place() .
With my page.place() sometimes a text frame with a table object will be returned, sometime a text frame with paragraphs where the cell contents is separated by tabs. I cannot see a reason why one thing should happen before the other one.

For my customer's VPN related problem:

Don't know if something will change, if I use textFrame.place() over page.place() .

I let you know…

Regards,
Uwe

Loic.Aigon
Legend
February 16, 2017

Funny enough I am involved in a XLSX based project and I could proceed the file with this :

var xlsx = File ( '/myFile.xlsx' );

var fo = Folder ( Folder.temp+"/myFolder" );

fo.create();

app.unpackageUCF ( xlsx, fo );

then you can introspect xml files

HTH

Loic