ID cards. Not business cards. That makes more sense. Yeah, AI’s Data Merge is very limited. For any flexibility you need to use scripting. Here’s a rudimentary script that takes an .ai document containing multiple named groups and inserts a line of CSV data into each. It uses the Layers palette (not the Variables palette) to name and number templated items. I have attached sample CSV and AI files that show how to set them up. Save it as “render cards.jsx”, choose File > Scripts > Other Script and select the script, and pick a CSV file when prompted. All going well, it will populate N template objects with the first N rows of the CSV. (If your AI document has 40 templates and your CSV has 300 rows, you’ll need to delete the first 40 rows after each run.) /******************************************************************************/
// render cards.jsx
// Render CSV rows into multiple templates in a single document.
// The template document must be frontmost in Illustrator before running the script.
// Each template is a *group* of text frames and other items.
// These groups must be named in the Layers palette as "card 1", "card 2", "card 3, etc".
//
// Text frames containing text to be replaced must be named in the Layers palette.
// A text frame's name must exactly match a CSV column name. Unnamed/Unrecognized text frames are ignored.
//
// The script could be expanded to relink images, set colors, remove empty elements, etc. This is left as an exercise.
// This code is released into the Public Domain.
(function() {
var TEMPLATE_PREFIX = "card ";
/******************************************************************************/
// helper functions
function readFile(f) {
f.encoding = 'utf-8';
if (!f.open('r')) { throw new Error("Can't open file: " + f.fullName); }
var s;
try {
s = f.read();
} catch (e) {
f.close();
throw e;
}
f.close();
return s;
}
function writeFile(f, s, isAppend) {
f.encoding = 'utf-8';
f.lineFeed = 'Windows';
if (!f.open(isAppend ? 'a' : 'w')) { throw new Error("Can't open file: " + f.fullName); }
var s;
try {
if (!isAppend) { f.write('\uFEFF'); }
f.write(s);
} catch (e) {
f.close();
throw e;
}
f.close();
}
/******************************************************************************/
// parse CSV file data
function parseCSVLine(data, headings) {
// Parses one line of CSV data, returning it and the remaining data
// data : string -- the CSV data
// headings : [string] | undefined -- undefined when reading first (headings) line
// Result: {fields: {string} | [string] | null, data: string} --the line's fields plus any remaining lines of CSV data
//
// Notes: On return, fields is an object if headings array is given (headings are used as object keys), or an array of fields if no headings are provided. The number of headings must match the number of CSV fields, otherwise an error is thrown.
var col = 0, eol = false; fields = headings ? {} : [];
var isEmpty = true;
while (!eol) { // find next comma or double quote, or end of line
var value = '', done = false, m = data.match(/[",]|[\r\n]+/);
switch ((m || '')[0]) {
case ',':
value = data.slice(0, m.index);
data = data.slice(m.index + 1); // step over text + comma
break;
case '"': // quoted field
data = data.slice(1); // discard opening quote
while (!done && data) {
m = data.match(/"/);
// note: quoted values can include line breaks so all we can do is read to next double-quote character
if (!m) { throw new Error("Missing double-quote at end of CSV column " + col + "."); }
var index = m.index;
value += data.slice(0, index);
if (data[index + 1] === '"') { // is the double quote followed by a second double quote (escape sequence)?
value += '"'; // replace the 2 double quotes with 1
index++;
} else { // it's a single double-quote, which ends the quoted value
done = true;
}
data = data.slice(index + 1); // step over quote(s)
}
if (data[0] === ',') {
data = data.slice(1); // step over trailing comma
} else {
data = data.replace(/^[\r\n]+/, ''); // strip trailing linebreak[s]
eol = true;
}
break;
default: // CR/LF = found end of line/end of file; last value is anything up to it
if (m) {
value = data.slice(0, m.index);
data = data.slice(m.index + m[0].length); // step over text and comma
} else {
value = data;
data = '';
}
eol = true;
}
if (headings) { // values is object with heading strings as keys
var heading = headings[col];
if (heading === undefined) {
throw new Error("Mismatched CSV columns. (Did you choose the right CSV file?) Expected " + headings.length
+ " columns but found a value in column " + col + ": '" + value + "'");
}
fields[heading] = value;
} else { // values is Array of heading strings
fields.push(value);
}
if (value) { isEmpty = false; }
col++;
if (col > 1000) { throw new Error("Too many CSV columns. (Did you choose the right CSV file?)"); }
}
if (headings && col !== headings.length) {
throw new Error("Mismatched CSV columns. (Did you choose the right CSV file?) Expected " + headings.length
+ " columns but found " + col + ".");
}
return {values: isEmpty ? null : fields, remainingData: data};
}
function parseCSVFile(csvFile) {
// Parse a UTF8-encoded .csv file. The first line must contain column headings.
// csvFile : File
// Result: [{field1:value1,...}]
var data = readFile(csvFile);
var result = parseCSVLine(data);
var remainingData = result.remainingData;
var headings = result.values;
var rows = [];
while (remainingData) {
var result = parseCSVLine(remainingData, headings);
var row = result.values;
if (row) { rows.push(row); } // ignore empty rows
remainingData = result.remainingData;
}
return rows;
}
/******************************************************************************/
function renderGroup(group, values) {
for (var i = 0; i < group.pageItems.length; i++) {
var item = group.pageItems[i];
var value = item.name ? values[item.name] : undefined;
switch (item.typename) {
case 'TextFrame':
if (value !== undefined) {
item.contents = value; // insert text into text frame
}
break;
case 'PlacedItem':
if (value !== undefined) {
// linking to a named image file is left as an exercise
}
break;
case 'GroupItem':
renderGroup(item, values);
break;
}
}
}
function renderCards(csvFile) {
if (!csvFile) { return; }
var doc = app.activeDocument;
var data = parseCSVFile(csvFile);
var templates = doc.layers[0].groupItems;
for (var i = 0; i < templates.length; i++) {
//var template = templates[i]; // iterate templates by stacking order
var template = templates.getByName(TEMPLATE_PREFIX + (i + 1)); // or by name, e.g. "card 1", "card 2",...
var values = data[i];
if (!template || !values) { break; }
renderGroup(template, values);
}
}
renderCards(File.openDialog("Please choose a .csv file:"));
})();
... View more