Javascript File Splitter compatible with Adobe Campaign 7.
Business case
You receive XML files that contain too many lines, you need to split them in multiple smaller files.
Orginal file:
<header>
<row></row>
<row></row>
<row></row>
<row></row>
<row></row>
</footer>
Will be split into 3 files with occurence="<row>"
and maxOccurencesPerFile=2
:
<!-- file_0001.xml -->
<header>
<row></row> <!-- occurence 1 -->
<row></row> <!-- occurence 2 -->
</footer>
<!-- file_0002.xml -->
<header>
<row></row> <!-- occurence 3 -->
<row></row> <!-- occurence 4 -->
</footer>
<!-- file_0003.xml -->
<header>
<row></row> <!-- occurence 5 -->
</footer>
Usage
loadLibrary('nms:fileSplitter');
var options = {
filename: vars.filename,
outputDir: vars.dir,
header: '<?xml version="1.0" encoding="UTF-8"?>\n<orders xmlns="http://www.demandware.com/xml/impex/customer/2006-10-31">',
occurence: '<order order-no',
footer: '</orders>',
ommitFirstHeader: true,
maxOccurencesPerFile: 50,
getNewFile: function(originalFile, nbOfFilesDone, options){
var actualFile = options.outputDir+'/'+originalFile.name.replace('.xml.txt', '')+"_"+nbOfFilesDone.toString().padStart(4, "0")+'.xml';
var f = new File(actualFile);
f.open("a");
logInfo('Created file '+f.fullName);
return f;
}
};
splitFile(options);
Source code
nms:fileSplitter
/**
* Splits a file such as:
* A
* A
* A
* into:
* File 1:
* header
* A
* A
* footer
* File 2:
* header
* A
* footer
*
* with params occurence="A", times=2
*/
loadLibrary('vendor:underscore'); // see https://blog.floriancourgey.com/2018/10/use-javascript-libraries-in-adobe-campaign/
const TAG = 'nms:fileSplitter | ';
function splitFile(options){
var defaultOptions = {
filename: null,
outputDir: null,
header: null,
occurence: null,
footer: null,
getNewFile: null,
ommitFirstHeader: true,
maxOccurencesPerFile: 10,
};
options = _.defaults(options, defaultOptions);
if(!options.filename || !options.outputDir || ! options.header || !options.occurence || !options.footer || !options.getNewFile){
return logError(TAG+'splitFile error, missing filename, outputDir, header, occurence, footer or getNewFile in options');
}
logInfo(TAG+'splitFile with options:');
logInfo(TAG+JSON.stringify(options));
var originalFile = new File(options.filename);
logInfo(TAG+'loading: '+options.filename)
var text = loadFile(options.filename);
logInfo(TAG+'splitting by newline')
var lines = text.split('\n');
logInfo(TAG+'ready to split into files');
var textToWrite = '';
if(!options.ommitFirstHeader){
textToWrite = options.header;
}
var nbOfFilesDone = 1;
var actualTime = 0;
var f = options.getNewFile(originalFile, nbOfFilesDone, options);
for(var i in lines){
var line = lines[i];
// if we find an occurence
if(line.indexOf(options.occurence) > -1){
// write
f.writeln(textToWrite)
textToWrite = '';
// if it's time to create a new file
if(actualTime >= options.maxOccurencesPerFile){
logInfo(TAG+'New file for occurence at line '+i+' for occurence('+actualTime+'/'+options.maxOccurencesPerFile+') for line ('+i+')')
// write footer and close file
f.writeln(options.footer)
f.close();
// reset vars
actualTime = 0;
nbOfFilesDone++;
// open the next file
f = options.getNewFile(originalFile, nbOfFilesDone, options);
/// adding header
if(nbOfFilesDone>1 || !options.ommitFirstHeader){
f.writeln(options.header)
}
}
actualTime++;
}
if(i > 0 && line.length>1){
textToWrite += '\n';
}
textToWrite += line;
}
// last one
f.writeln(textToWrite);
f.close();
logInfo(TAG+'finished');
return;
}
NB: to include underscoreJS library, see https://blog.floriancourgey.com/2018/10/use-javascript-libraries-in-adobe-campaign/
Polyfills for padStart()
in Adobe Campaign
Note: to make it work with Adobe Campaign, add the following polyfills for padStart()
in nms:polyfill
:
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/repeat
if (!String.prototype.repeat) {
String.prototype.repeat = function(count) {
'use strict';
if (this == null) {
throw new TypeError('can\'t convert ' + this + ' to object');
}
var str = '' + this;
count = +count;
if (count != count) {
count = 0;
}
if (count < 0) {
throw new RangeError('repeat count must be non-negative');
}
if (count == Infinity) {
throw new RangeError('repeat count must be less than infinity');
}
count = Math.floor(count);
if (str.length == 0 || count == 0) {
return '';
}
// Ensuring count is a 31-bit integer allows us to heavily optimize the
// main part. But anyway, most current (August 2014) browsers can't handle
// strings 1 << 28 chars or longer, so:
if (str.length * count >= 1 << 28) {
throw new RangeError('repeat count must not overflow maximum string size');
}
var rpt = '';
for (var i = 0; i < count; i++) {
rpt += str;
}
return rpt;
}
}
// https://github.com/uxitten/polyfill/blob/master/string.polyfill.js
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/padStart
if (!String.prototype.padStart) {
String.prototype.padStart = function padStart(targetLength,padString) {
targetLength = targetLength>>0; //truncate if number or convert non-number to 0;
padString = String((typeof padString !== 'undefined' ? padString : ' '));
if (this.length > targetLength) {
return String(this);
}
else {
targetLength = targetLength-this.length;
if (targetLength > padString.length) {
padString += padString.repeat(targetLength/padString.length); //append to original to ensure we are longer than needed
}
return padString.slice(0,targetLength) + String(this);
}
};
}
Then add loadLibrary('nms:polyfill');
in your main file.