Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dynamic variables in key names #40

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 42 additions & 14 deletions lib/builder.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ var builder = {
var _descriptor = extracter.splitXml(xmlWithoutMarkers, _dynamicDescriptor);
_descriptor = extracter.buildSortedHierarchy(_descriptor);
_descriptor = extracter.deleteAndMoveNestedParts(_descriptor);
var _builder = builder.getBuilderFunction(_descriptor, options.formatters);
var _builder = builder.getBuilderFunction(_descriptor, options.formatters, variables);
var _obj = {
d : data,
c : options.complement
Expand Down Expand Up @@ -351,10 +351,17 @@ var builder = {
* This array of strings will be sorted and assemble by assembleXmlParts to get final result.
*
* @param {object} descriptor : data descriptor computed by the analyzer
* @param {object} existingFormatters - a map of defined formatter functions
* @param {object[]} variables - an array of variable descriptors to use for dynamic variable substitutions
* @return {function} f
*/
getBuilderFunction : function (descriptor, existingFormatters) {
getBuilderFunction : function (descriptor, existingFormatters, variables) {
var that = this;
var variableNamesToPaths = {};
variables = variables || {};
for (var i = 0; i < variables.length; i += 1) {
variableNamesToPaths[variables[i].name] = variables[i].code;
}
// declare an object which will contain all the code (by section) of the generated function
var _code = {
init : '',
Expand Down Expand Up @@ -419,7 +426,7 @@ var builder = {
// For each object, generate the code
for (var _objIndex = 0; _objIndex < _hierarchy.length; _objIndex++) {
var _objName = _hierarchy[_objIndex];
var _realObjName = _dynamicData[_objName].name;
var _realObjKey = resolveKeyToExpression(_dynamicData[_objName].name, variableNamesToPaths);
var _type = _dynamicData[_objName].type;
var _objParentName = _dynamicData[_objName].parent;
var _xmlParts = _dynamicData[_objName].xmlParts;
Expand All @@ -439,13 +446,13 @@ var builder = {
if (_type === 'object') {
// declare any nested object
if (_objName!=='_root') {
_code.add('prev','main', _objName+'='+'('+_objParentName+' !== undefined)?'+_objParentName+'[\''+_realObjName+'\']:{};\n');
_code.add('prev','main', _objName+'='+'('+_objParentName+')?'+_objParentName+'['+_realObjKey+']:{};\n');
}
}
else if (_type === 'objectInArray') {
var _arrayOfObjectName = _objName+'_array';
var _arrayOfObjectIndexName = _objName+'_i';
_code.add('prev','main', 'var ' + _arrayOfObjectName+'='+'('+_objParentName+' !== undefined)? '+_objParentName+'[\''+_realObjName+'\']:[];\n');
_code.add('prev','main', 'var ' + _arrayOfObjectName+'='+'('+_objParentName+')? '+_objParentName+'['+_realObjKey+']:[];\n');
var _conditionToFindObject = _dynamicData[_objName].conditions || [];
var _objNameTemp = _objName+'_temp';

Expand All @@ -471,7 +478,7 @@ var builder = {

// declare any nested object
if (_objName!=='_root') {
_code.add('prev', 'main', 'var '+_arrayName+'='+ _objParentName+"['"+_realObjName+"'];\n");
_code.add('prev', 'main', 'var '+_arrayName+'='+ _objParentName+'['+_realObjKey+'];\n');
}
else {
_code.add('prev', 'main', 'var '+_arrayName+'='+ _objName+';\n');
Expand Down Expand Up @@ -516,12 +523,12 @@ var builder = {
var _iteratorObjName = _objName+'_itObj';
_code.add('prev', 'main', ' var '+_iteratorName+' = 0;\n'); // TODO return errors if the iterator is not defined
if (_containSpecialIterator===true) {
_code.add('main', 'if('+ _objName +' !== undefined){\n');
_code.add('main', 'if('+ _objName +'){\n');
}
// the iterator is inside an object
if (_iterator.obj !== undefined) {
_code.add('prev','main', ' var '+_iteratorObjName+' = '+_objName+"['"+_iterator.obj+"'];\n");
_code.add('prev','main', ' if('+_iteratorObjName+' !== undefined) {\n');
_code.add('prev','main', ' if('+_iteratorObjName+') {\n');
_code.add('prev','main', ' '+_iteratorName+' = '+_iteratorObjName+"['"+_iterator.attr+"'];\n");
_code.add('prev','main', ' }\n');
}
Expand Down Expand Up @@ -551,11 +558,10 @@ var builder = {
for (var i = 0; i < _xmlParts.length; i++) {
var _xmlPart = _xmlParts[i];
var _dataObj = _xmlPart.obj;
var _dataAttr = _xmlPart.attr;
var _dataAttr = resolveKeyToExpression(_xmlPart.attr, variableNamesToPaths);
var _partDepth = _xmlPart.depth;
var _formatters = _xmlPart.formatters || [];
var _conditions = _xmlPart.conditions || [];

// keep highest position for the last xml part
if (_xmlPart.pos > _keepHighestPosition) {
_keepHighestPosition = _xmlPart.pos;
Expand All @@ -580,8 +586,8 @@ var builder = {
// handle conditions
_code.add('main', '_strPart.rowShow = true;\n');
_code.add('main', that.getFilterString(_conditions, '_strPart.rowShow = false', _objName, true));
_code.add('main', 'if('+ _dataObj +' !== undefined){\n');
_code.add('main', 'var _str = ' + _dataObj + "['" + _dataAttr + "']" + ';\n');
_code.add('main', 'if('+ _dataObj +'){\n');
_code.add('main', 'var _str = ' + _dataObj + "[" + _dataAttr + "]" + ';\n');
_code.add('main', '_formatterOptions.stopPropagation = false;\n');
_code.add('main', that.getFormatterString('_str', '_formatterOptions', _formatters, existingFormatters, false));
// replace null or undefined value by an empty string
Expand Down Expand Up @@ -653,6 +659,30 @@ var builder = {

module.exports = builder;

/**
* Turn a key (a string that's used as an object index) into an expression
* to be evaluated in the generated builder function.
* @param {string} objectName - Name used as the index, for example if the reference was "item.name" then this will be "name".
* @param {object.<string,string>} variableMap - A map that has known variable names as keys and the target expressions as values. For example, { alias1: 'd.value1' } will result in references $$alias1 to be evaluated at run-time as _root.d.value1.
*/
function resolveKeyToExpression(objectName, variableMap) {
// Keep non-string values as they are - some logic in the builder function
// relies on checking if name parts/keys are null or falsy.
if (typeof objectName !== 'string') {
return objectName;
}
var matches = objectName.match(/^\$\$([a-zA-Z0-9_]+)$/);
if (!matches || matches.length < 2) {
// This is a normal key reference (.key)
return '\'' + objectName + '\'';
}
// This is a dynamic key reference (.$$var) and should be evaluated in the generated function.
var variableName = matches[1];
if (!variableMap[variableName]) {
return '\'$$' + variableName + '\'';
}
return '(function() { try { return _root.' + variableMap[variableName] + '; } catch (error) { return \'\'; } })()';
}

/**
* Test if two arrays are equal
Expand Down Expand Up @@ -704,5 +734,3 @@ function removeFrom (arr, value) {
}
return arr;
}


8 changes: 4 additions & 4 deletions lib/parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,7 @@ var parser = {
_code = _code.replace(new RegExp('\\'+_param.name,'g'), '_$'+_param.index+'_');
}
}
var _regex = new RegExp('\\$'+_variable+'(?:\\(([\\s\\S]+?)\\))?', 'g');
var _regex = new RegExp('(?:^|[^\\$])(\\$' + _variable + '(?:\\(([\\s\\S]+?)\\))?)', 'g');
existingVariables.push({
name : _variable,
code : _code,
Expand Down Expand Up @@ -263,8 +263,8 @@ var parser = {
var _pattern = _variable.regex.exec(_markerName);
while (_pattern !== null) {
var _code = _variable.code;
var _varStr = _pattern[0];
var _paramStr = _pattern[1];
var _varStr = _pattern[1];
var _paramStr = _pattern[2];
// if there some parameters?
if (_paramStr !== undefined) {
// separate each parameters
Expand Down Expand Up @@ -529,4 +529,4 @@ var parser = {

};

module.exports = parser;
module.exports = parser;
66 changes: 66 additions & 0 deletions test/test.builder.buildXML.js
Original file line number Diff line number Diff line change
Expand Up @@ -1454,6 +1454,72 @@ describe('builder.buildXML', function () {
done();
});
});
it('should resolve dynamic keys in leaf using variables', function (done) {
var _xml = '<xml> {#style = d.style}{d.company.names.$$style} </xml>';
var _data = {
style: 'short',
company: {
names: { short: 'Snakeoil', full: 'Snakeoil Systems GmbH' }
}
};
builder.buildXML(_xml, _data, function (err, _xmlBuilt) {
assert.equal(_xmlBuilt, '<xml> Snakeoil </xml>');
done();
});
});
it('should resolve dynamic keys in node using variables', function (done) {
var _xml = '<xml> Payer name: {#payerParty = d.payer}{d.companies.$$payerParty.name} </xml>';
var _data = {
payer: 'seller',
companies: {
buyer: { name: 'General Store' },
seller: { name: 'ACME' }
}
};
builder.buildXML(_xml, _data, function (err, _xmlBuilt) {
assert.equal(_xmlBuilt, '<xml> Payer name: ACME </xml>');
done();
});
});
it('should resolve values of empty dynamic keys as if the key name was undefined', function (done) {
var _xml = '<xml> {#style = d.style}{d.company.names.$$style} </xml>';
var _data = {
// NOTE: we don't provide "style"
company: {
names: { short: 'Snakeoil', full: 'Snakeoil Systems GmbH' }
}
};
builder.buildXML(_xml, _data, function (err, _xmlBuilt) {
assert.equal(_xmlBuilt, '<xml> </xml>');
done();
});
});
it('should follow normal value resolution rules when object is undefined', function (done) {
var _xml = '<xml> Payer name: {#payerParty = d.payer}{d.companies.$$payerParty.name} </xml>';
var _data = {
payer: 'nobody-today-is-tax-free-day',
companies: {
buyer: { name: 'General Store' },
seller: { name: 'ACME' }
}
};
builder.buildXML(_xml, _data, function (err, _xmlBuilt) {
assert.equal(_xmlBuilt, '<xml> Payer name: </xml>');
done();
});
});
it('should only resolve known variables to allow using verbatim keys with two dollar signs', function (done) {
var _xml = '<xml> {d.products.$$saver} </xml>';
var _data = {
products: {
$$saver: 'save 10% today'
}
};
builder.buildXML(_xml, _data, function (err, _xmlBuilt) {
assert.equal(_xmlBuilt, '<xml> save 10% today </xml>');
done();
});
});
/* it.skip('should not crash if the markes are not correct (see comment below)');*/
/*
[
Expand Down