carbone
Version:
Fast, Simple and Powerful report generator. Injects JSON and produces PDF, DOCX, XLSX, ODT, PPTX, ODS, ...!
798 lines (756 loc) • 30.5 kB
JavaScript
var parser = require('./parser');
var helper = require('./helper');
var extracter = {
/**
* Split markers in order to build a usable descriptor
*
* @param {array} markers : array of markers with their position
* @return {object} descriptor
*/
splitMarkers : function (markers) {
var _res = {};
var _visitedArray = {};
for (var i = 0; i < markers.length; i++) {
var _marker = markers[i].name;
var _markerPos = markers[i].pos;
var _word = '';
var _prevMarker = '';
var _objectNameWhereStartMultiplePoints = '';
var _parents = [];
var _uniqueMarkerName = '';
var _uniqueArrayName = '';
var _arrayName = '';
var _iterators = [];
var _prevChar = '';
var _repeater = '';
var _isFormatter = false;
var _isFormatterParenthesis = false;
var _isString = false;
var _formaters = [];
var _formatterStr = '';
var _isArray = false;
var _conditions = [];
var _isIteratorUsedInCurrentArray = false;
var _conditionsToFindObjectInArray = [];
// Loop on each char.
// We do not use regular expressions because it is a lot easier like that.
for (var c = 0; c < _marker.length; c++) {
var _char = _marker.charAt(c);
// if we enter in a string (it will keep whitespaces)
if (_char === "'" || _char === '"') {
_isString = !_isString;
}
// if we enter in a formatter and we are not between two parenthesis of a formatter (the character ":" is allowed within the parenthesis)
if (_char===':' && _isFormatterParenthesis === false) {
_isFormatter = true;
// if we enter in a second formatter, keep the previous one. Ex. three nested formaters : formater1:formater2:formater3
if (_formatterStr !== '') {
_formaters.push(_formatterStr);
_formatterStr = '';
}
}
// if we are in a formatter, and if we enter in a parenthesis
else if (_isFormatter === true && _char==='(') {
_formatterStr += _char;
_isFormatterParenthesis = true;
}
// if we are in a formatter and we exit
else if (_isFormatter === true && _char===')') {
_formatterStr += _char;
_isFormatterParenthesis = false;
_isFormatter = false;
_formaters.push(_formatterStr);
_formatterStr = '';
}
else if (_isFormatter === true && (_char!==' ' || _isString===true) ) {
_formatterStr += _char;
}
// if we enter into the brakets of an array
else if (_char==='[') {
_isIteratorUsedInCurrentArray = false;
_isArray = true;
_uniqueMarkerName += _word;
_uniqueMarkerName = this._cleanUniqueMarkerName(_uniqueMarkerName);
_arrayName = _word;
// If we are in a multidimensional array
if (_prevChar === ']') {
_uniqueMarkerName += '_';
}
_uniqueArrayName = _uniqueMarkerName;
_uniqueMarkerName += _visitedArray[_uniqueArrayName] || '';
_word = '';
_isFormatter = false;
}
// if we multipe points are used
else if (_char === '.' && _prevChar === '.') {
if (_objectNameWhereStartMultiplePoints === '') {
_objectNameWhereStartMultiplePoints = _prevMarker;
}
if (_parents.length === 0) {
throw Error('Cannot access parent object in "'+_marker+'" (too high)');
}
if (_uniqueMarkerName === _parents[_parents.length-1]) {
// special case when coming from an array
_parents.pop();
}
_uniqueMarkerName = _parents.pop();
_prevMarker = _uniqueMarkerName;
if (_parents.length > 0) {
_prevMarker = _parents[_parents.length-1];
}
}
// if we enter into an object
else if (_char ==='.' && _isArray === false) {
if (_objectNameWhereStartMultiplePoints !== '') {
// when coming from .., set _prevMarker as usual
_prevMarker = _uniqueMarkerName;
}
_uniqueMarkerName += _word;
_uniqueMarkerName = this._cleanUniqueMarkerName(_uniqueMarkerName);
if (_prevMarker !== '' && _parents[_parents.length-1] !== _prevMarker) {
_parents.push(_prevMarker);
}
if (!_res[_uniqueMarkerName]) {
_res[_uniqueMarkerName]={
name : _word,
type : 'object',
parent : _prevMarker,
parents : _parents.slice(0),
xmlParts : []
};
}
_prevMarker = _uniqueMarkerName;
_word = '';
_isFormatter = false;
}
// if this is not a whitespace, keep the word
else if (_char!==' ' || _isString===true) {
_word += _char;
}
if (_isArray === true) {
// detect array condition, or direct access in arrays such as myAarray[10]
var _result = /([^,!]+)(!=|=|>|<)([\s\S]+)[,\]]$/.exec(_word) || /(\d+)\]$/.exec(_word);
// detect direct
if (_result instanceof Array) {
if (_result.length === 2) {
// if this is a direct access such as "myArray[10]", adapt the result as if it was "myArray[i=10]"
_result = [0, 'i', '=', _result[1]];
}
var _operator = (_result[2] === '=') ? '==' : _result[2];
var _conditionObj = {
left : {parent : _uniqueMarkerName, attr : _result[1]},
operator : _operator,
right : _result[3]
};
_conditions.push(_conditionObj);
_word = '';
}
else if (_word.length > 1 && (_char===',' || _char===']')) {
_isIteratorUsedInCurrentArray = true; // +1 or not
if (_word[0] === '*') {
_repeater = _word.slice(1, -1);
_word = _char;
}
}
// detect array iteration
if (_word.substr(-2)==='+1') {
var _iteratorNormal = _word.slice(0,-2).replace(',','');
_iterators.push({str : _iteratorNormal});
_word = '';
}
else if (_word.substr(-2)==='++') {
var _iteratorCustom = _word.slice(0,-2).replace(',','');
_iterators.push({str : _iteratorCustom, isSpecial : true});
_word = '';
}
}
// if we exit the bracket of an array
if (_char===']' && _isArray===true) {
// If it was the first part of the array (i without "i+1")
if (_iterators.length === 0) {
// If we detect new start of an already known array
if (_res[_uniqueMarkerName] && _res[_uniqueMarkerName].position.end !== undefined) {
if (!_visitedArray[_uniqueArrayName]) {
_visitedArray[_uniqueArrayName] = '';
}
_visitedArray[_uniqueArrayName] += '$';
// correction of previously detected conditions
for (var k = 0; k < _conditions.length; k++) {
var _condition = _conditions[k];
if (_condition.left.parent===_uniqueMarkerName) {
_condition.left.parent += '$';
}
}
_uniqueMarkerName += '$';
}
if (_prevMarker !== '' && _parents[_parents.length-1] !== _prevMarker) {
_parents.push(_prevMarker);
}
if (_isIteratorUsedInCurrentArray === false) {
var _filterUniqueName = '';
var h = _conditions.length - 1;
for (; h >= 0 ; h--) {
var _conditionToFindObject = _conditions[h];
_filterUniqueName += helper.cleanJavascriptVariable(_conditionToFindObject.left.attr + '__' + _conditionToFindObject.right);
// consider only conditions of this array, thus only last rows which match with unique array name
if (_conditionToFindObject.left.parent !== _uniqueMarkerName ) {
break;
}
}
// extract conditions to find this object
_conditionsToFindObjectInArray = _conditions.slice(h + 1, _conditions.length);
_conditions = _conditions.slice(0, h + 1);
// generate a new object
_uniqueMarkerName += _filterUniqueName;
_uniqueMarkerName = this._cleanUniqueMarkerName(_uniqueMarkerName);
// correction of previously detected conditions
for (var m = 0; m < _conditionsToFindObjectInArray.length ; m++) {
_conditionsToFindObjectInArray[m].left.parent += _filterUniqueName;
}
}
// create the new array
if (!_res[_uniqueMarkerName]) {
_res[_uniqueMarkerName]={
name : _arrayName,
type : (_isIteratorUsedInCurrentArray === true) ? 'array' : 'objectInArray',
parent : _prevMarker,
parents : _parents.slice(0),
position : {},
iterators : [],
xmlParts : []
};
}
if (_isIteratorUsedInCurrentArray === true && _res[_uniqueMarkerName].position.start === undefined) {
_res[_uniqueMarkerName].position.start = _markerPos;
}
if (_isIteratorUsedInCurrentArray === false) {
_res[_uniqueMarkerName].conditions = _conditionsToFindObjectInArray;
_conditionsToFindObjectInArray = [];
}
_prevMarker = _uniqueMarkerName;
}
// If it was the second part of the array ("i+1")
else {
if (_res[_uniqueMarkerName].position.end === undefined) {
_res[_uniqueMarkerName].position.end = _markerPos;
if (_repeater) {
_res[_uniqueMarkerName].repeater = _repeater;
_repeater = '';
}
var _prevIteratorStr = '';
for (var it = 0; it < _iterators.length; it++) {
var _iterator = _iterators[it];
var _iteratorStr = _iterator.str;
if (_prevIteratorStr !== _iteratorStr) {
var _iteratorInfo = {};
// if the iterator is in a sub-object. Ex: movie.sort+1
if (/\./.test(_iteratorStr)===true) {
var _splitIterator = _iteratorStr.split('.');
_iteratorInfo = {obj : _splitIterator[0],attr : _splitIterator[1]};
}
else {
_iteratorInfo = {attr : _iteratorStr};
}
if (_iterator.isSpecial===true) {
_iteratorInfo.isSpecial = true;
_res[_uniqueMarkerName].containSpecialIterator = true;
}
_res[_uniqueMarkerName].iterators.push(_iteratorInfo);
}
_prevIteratorStr = _iteratorStr;
}
}
}
_word = '';
_isFormatter = false;
_isArray = false;
}
// remove whitespaces
if (_char!==' ') {
_prevChar = _char;
}
}
// do not store the odd part of an array
if (_iterators.length === 0) {
// If there is a formatter, add it in the array of formatters
if (_formatterStr !== '') {
_formaters.push(_formatterStr);
}
var _objParent = _prevMarker;
var _objOwner = _prevMarker;
// If the the user uses multiple points ...
if (_objectNameWhereStartMultiplePoints !== '') {
_objOwner = _objectNameWhereStartMultiplePoints;
_objParent = _uniqueMarkerName;
}
var _xmlPart = {
attr : _word,
formatters : _formaters,
obj : _objParent,
pos : _markerPos,
posOrigin : _markerPos
};
if (_conditions.length > 0) {
_xmlPart.conditions = _conditions;
}
_res[_objOwner].xmlParts.push(_xmlPart);
}
}
return _res;
},
/**
* Remove dash from string to avoir errors
* @param {String} markerName Marker name to clean
*/
_cleanUniqueMarkerName : function (markerName) {
return markerName.replace(/-/g, '');
},
/**
* Sort xml parts. Used in splitXml
* @param {Array} xmlParts (modified by the function)
*/
sortXmlParts : function (xmlParts) {
xmlParts.sort(function (a,b) {
if (Math.trunc(a.pos) > Math.trunc(b.pos)) {
return 1;
}
if (Math.trunc(a.pos) < Math.trunc(b.pos)) {
return -1;
}
// When two arrays are incremented in the same time (d.tab[i+1].subtab[i+1]), pos is the same...
// I use the length of the string to garuantee that the loop "d.tab" is before "d.tab.subtab".
if (a.array==='start' && b.array==='start') {
return (a.obj.length - b.obj.length);
}
if (a.array==='end' && b.array==='end' ) {
return (b.obj.length - a.obj.length);
}
// after detecting arrays and conditional blocks, some markers may have moved in XML
// It could create conflicts (many markers at the same XML position).
// So, we sort them using their original position
if (a.posOrigin > b.posOrigin) {
return 1;
}
if (a.posOrigin < b.posOrigin) {
return -1;
}
// other basic cases:
if (a.array==='start') {
return -1;
}
if (b.array==='start') {
return 1;
}
if (a.array==='end') {
return 1;
}
if (b.array==='end') {
return -1;
}
return 0;
});
},
/**
* Divide up the xml in the descriptor
*
* @param {string} xml : pure xml withous markers
* @param {object} descriptor : descriptor generated by the function splitMarkers
* @return {object} descriptor with xml inside
*/
splitXml : function (xml, descriptor) {
const MARKER_PRESENCE_CHAR_REGEX = /\uFFFF/g;
var _res = {
staticData : {
before : '',
after : ''
},
dynamicData : descriptor
};
findAndSetValidPositionOfConditionalBlocks(xml, descriptor);
// Find the exact positions of all arrays
findAndSetExactPositionOfArrays(xml, descriptor);
// Extract all parts independently
var _allSortedParts = getAllParts(descriptor);
// And sort them by ascendant positions
extracter.sortXmlParts(_allSortedParts);
// if all parts are empty, return all the xml
if (_allSortedParts.length === 0) {
_res.staticData.before = xml.slice(0).replace(MARKER_PRESENCE_CHAR_REGEX,'');
}
// Slice the xml
var _prevPos = 0;
var _prevPart = {};
var _arrayDepth = 0;
var _currentlyVisitedArray = [];
var _lastVisitedArrayPos = {};
// avoid having two (or more) markers at the same XML position for the final sort in builder.assembleXmlParts
// fix this using float position.
for (let i = 0; i < _allSortedParts.length; i++) {
let _part = _allSortedParts[i];
if (Math.trunc(_part.pos) === Math.trunc(_prevPart.pos)) {
// Why adding 1/64? to avoid rounding problems of floats (http://0.30000000000000004.com)
// This let us the possibility to have 64 markers at the same position. It should enough for all cases.
_part.pos = _prevPart.pos + 1/64;
// fix also beginning and ending of arrays
if (_part.array === 'start') {
descriptor[_part.obj].position.start = _part.pos;
}
else if (_part.array === 'end') {
descriptor[_part.obj].position.end = _part.pos;
}
}
_prevPart = _part;
}
_prevPart = {};
for (var i = 0; i < _allSortedParts.length; i++) {
var _part = _allSortedParts[i];
var _pos = Math.trunc(_part.pos); // can be a float
if (i===0) {
_res.staticData.before = xml.slice(_prevPos, _pos).replace(MARKER_PRESENCE_CHAR_REGEX,'');
}
if (_prevPart.array === 'start') {
_arrayDepth++;
_prevPart.after = xml.slice(_prevPos, _pos).replace(MARKER_PRESENCE_CHAR_REGEX,'');
_prevPart.depth = _arrayDepth;
_currentlyVisitedArray.push(_prevPart.obj);
_lastVisitedArrayPos = descriptor[_prevPart.obj].position;
}
_part.depth = _arrayDepth;
if (_part.array === 'end') {
descriptor[_part.obj].depth = _arrayDepth;
_arrayDepth--;
_part.before = xml.slice(_prevPos, _pos ).replace(MARKER_PRESENCE_CHAR_REGEX,'');
}
// move or remove part which are not in the repetition section
for (var j = _currentlyVisitedArray.length - 1; j >= 0; j--) {
var _lastVisitedArrayName = _currentlyVisitedArray[j];
var _arrayPos = descriptor[_lastVisitedArrayName].position;
var _partParents = descriptor[_part.obj].parents;
// if the part belongs to the current visited array, keep it in this array.
if (_part.pos <= _arrayPos.end && (_part.depth === _arrayDepth && _lastVisitedArrayName === _part.obj || _part.array === 'start' || _part.array === 'end' || _partParents.indexOf(_lastVisitedArrayName) !== -1)) {
break;
}
if (_part.pos > _arrayPos.start && _part.pos < _arrayPos.end && _partParents.indexOf(_lastVisitedArrayName) === -1 && _lastVisitedArrayName !== _part.obj) {
_part.moveTo = _lastVisitedArrayName;
break;
}
else if (Math.trunc(_part.pos) > Math.trunc(_arrayPos.end) && Math.trunc(_part.pos) < Math.trunc(_arrayPos.endOdd)) {
_part.toDelete = true; // remove all parts which are in odd zone of arrays
break;
}
}
// when we leave completely a repetition section, update _lastVisitedArrayPos and _currentlyVisitedArray
if (_arrayDepth > 0 && _part.pos >= _lastVisitedArrayPos.endOdd && _part.pos > _lastVisitedArrayPos.end) {
_currentlyVisitedArray.pop();
if (_currentlyVisitedArray.length > 0) {
_lastVisitedArrayPos = descriptor[_currentlyVisitedArray[_currentlyVisitedArray.length-1]].position;
}
else {
_lastVisitedArrayPos = {};
}
}
if (!_prevPart.array && _part.array !== 'end') {
let _xmlPart = xml.slice(_prevPos, _pos ).replace(MARKER_PRESENCE_CHAR_REGEX,'');
// if toDelete = true, we are in the odd part of the array. This xml part will be removed
if (_prevPart.toDelete !== true) {
_prevPart.after = _xmlPart;
}
else if (_part.toDelete !== true) {
_part.before = _xmlPart;
}
}
if (_prevPart.array && _prevPart.array === 'end') {
if (_prevPos!==_pos) {
// if we have two adjacent arrays, keep the xml which is between in two arrys
if (_part.array && _part.array==='start') {
descriptor[_part.obj].before = xml.slice(_prevPos, _pos).replace(MARKER_PRESENCE_CHAR_REGEX,'');
}
else {
_part.before = xml.slice(_prevPos, _pos).replace(MARKER_PRESENCE_CHAR_REGEX,'');
}
}
}
// if there is a dead zone (ex. odd part of the array), jump to the next part
if (_part.array === 'end') {
_prevPos = Math.trunc(descriptor[_part.obj].position.endOdd);
}
else if (_pos > _prevPos) {
_prevPos = _pos;
}
// the end, put the last xml part in the staticData object
if (i === _allSortedParts.length-1) {
_res.staticData.after = xml.slice(_prevPos).replace(MARKER_PRESENCE_CHAR_REGEX,'');
}
_prevPart = _part;
}
return _res;
},
/**
* Generate an array which contains in which order the builder should travel the dynamicData
*
* @param {object} descriptor : descriptor returned by splitXml
* @return {object} descriptor with the hierarchy array
*/
buildSortedHierarchy : function (descriptor) {
var _sortedNodes = [];
// find all node which have parents
var _hasChildren = getAllNodeWhichHaveParents(descriptor);
// keep only leaves of the tree
var _treeLeaves = getAllLeaves(_hasChildren, descriptor);
// update treeLeave, compute depth and unique branch name
computeDepthAndBranchNames(_treeLeaves, descriptor);
// sort by depth
_treeLeaves.sort(sortByDepth);
function insertIfNotExists (table, node) {
for (var i = 0; i < table.length; i++) {
if (table[i] === node) {
return;
}
}
table.push(node);
}
for (var i = 0; i < _treeLeaves.length; i++) {
var _obj = _treeLeaves[i];
for (var j = _obj.parents.length - 1; j >= 0 ; j--) {
insertIfNotExists(_sortedNodes, _obj.parents[j]);
}
insertIfNotExists(_sortedNodes, _obj.objName);
}
descriptor.hierarchy = _sortedNodes;
return descriptor;
},
/**
* delete and move xmlPart which are not in the right place
*
* @param {object} descriptor : descriptor returned by splitXml
* @return {object} descriptor with modified xmlParts
*/
deleteAndMoveNestedParts : function (descriptor) {
for (var _objName in descriptor.dynamicData) {
var _xmlParts = descriptor.dynamicData[_objName].xmlParts;
for (var i = 0; i < _xmlParts.length; i++) {
var _part = _xmlParts[i];
if (_part.moveTo !== undefined) {
_xmlParts.splice(i--, 1);
descriptor.dynamicData[_part.moveTo].xmlParts.push(_part);
delete _part.moveTo;
}
else if (_part.toDelete === true) {
delete _part.toDelete;
_xmlParts.splice(i--, 1);
}
}
}
return descriptor;
}
};
module.exports = extracter;
/**
* @param {String} xml xml to parse
* @param {Object} descriptor descriptor generated by the function splitMarkers
* @return {Array}
*/
function findAndSetValidPositionOfConditionalBlocks (xml, descriptor) {
// TODO performance: flattenXML should be called earlier and used also to find array positions
var _xmlFlattened = parser.flattenXML(xml);
for (var _objName in descriptor) {
var _xmlParts = descriptor[_objName].xmlParts;
var _conditionalBlockDetected = [];
var _newParts = [];
for (var i = 0; i < _xmlParts.length; i++) {
var _part = _xmlParts[i];
var _formatters = _part.formatters || [];
var _nbFormatters = _formatters.length;
if (_nbFormatters === 0) {
continue;
}
var _lastFormatter = _formatters[_nbFormatters - 1];
if (_lastFormatter.indexOf('showBegin') !== -1 || _lastFormatter.indexOf('hideBegin') !== -1) { // TODO return error if not last
_conditionalBlockDetected.push(_part);
}
else if (_lastFormatter.indexOf('showEnd') !== -1 || _lastFormatter.indexOf('hideEnd') !== -1) {
var _beginPart = _conditionalBlockDetected.pop();
if (_beginPart === undefined) {
throw new Error('Missing at least one showBegin or hideBegin');
}
// convert XML string into an array and find the array index of beginTextIndex
var _safeXMLBlocks = parser.findSafeConditionalBlockPosition(_xmlFlattened, _beginPart.pos, _part.pos);
if (_safeXMLBlocks.length > 0) {
_beginPart.posOrigin = _beginPart.pos;
_part.posOrigin = _part.pos;
_beginPart.pos = _safeXMLBlocks[0][0];
_part.pos = _safeXMLBlocks[0][1];
for (var j = 1; j < _safeXMLBlocks.length; j++) {
var _safeXMLBlock = _safeXMLBlocks[j];
_newParts.push({ obj : _beginPart.obj, formatters : _beginPart.formatters, attr : _beginPart.attr, pos : _safeXMLBlock[0], posOrigin : _beginPart.posOrigin, conditions : _beginPart.conditions });
_newParts.push({ obj : _beginPart.obj, formatters : _part.formatters , attr : _beginPart.attr, pos : _safeXMLBlock[1], posOrigin : _part.posOrigin , conditions : _beginPart.conditions });
}
}
}
}
if (_conditionalBlockDetected.length > 0) {
throw new Error('Missing at least one showEnd or hideEnd');
}
descriptor[_objName].xmlParts = descriptor[_objName].xmlParts.concat(_newParts);
}
}
/**
* Find begin and end of each part to repeat, and add info in descriptor
* @param {String} xml xml to parse
* @param {Object} descriptor descriptor generated by the function splitMarkers
* @return {Array} modify descriptor and return an array of odd parts
*/
function findAndSetExactPositionOfArrays (xml, descriptor) {
var _oddZones = [];
for (var _objName in descriptor) {
var _obj = descriptor[_objName];
if (_obj.type === 'array' && _obj.iterators.length>0) {
var _roughPosStart = descriptor[_objName].position.start;
var _roughPosEnd = descriptor[_objName].position.end;
var _subString = xml.slice(Math.trunc(_roughPosStart), Math.trunc(_roughPosEnd));
var _pivot = parser.findPivot(_subString);
// TODO test if the _pivot is not null !!!!!!!!!!!!
// absolute position of the pivot
_pivot.part2Start.pos += _roughPosStart;
_pivot.part1End.pos += _roughPosStart;
var _realArrayPosition = parser.findRepetitionPosition(xml, _pivot, _roughPosStart);
// TODO test if the repetition is found !!!!!!!!!!!!
descriptor[_objName].position = {start : _realArrayPosition.startEven, end : _realArrayPosition.endEven, endOdd : _realArrayPosition.endOdd};
descriptor[_objName].xmlParts.push({obj : _objName, array : 'start', pos : _realArrayPosition.startEven, posOrigin : _roughPosStart});
descriptor[_objName].xmlParts.push({obj : _objName, array : 'end' , pos : _realArrayPosition.endEven , posOrigin : _roughPosEnd });
_oddZones.push([_realArrayPosition.endEven, _realArrayPosition.endOdd]);
}
}
return _oddZones;
}
/**
* Return an array of all parts of the descriptor
* @param {Object} descriptor descriptor generated by the function splitMarkers
* @return {Array} all parts in one array
*/
function getAllParts (descriptor) {
var _allSortedParts = [];
for (var _objName in descriptor) {
var _xmlParts = descriptor[_objName].xmlParts;
for (var i = 0; i < _xmlParts.length; i++) {
var _part = _xmlParts[i];
_allSortedParts.push(_part);
}
}
return _allSortedParts;
}
/**
* get all node which have parents
* @param {Object} descriptor descriptor generated by the function splitMarkers
* @return {Object} the keys are a list of parent's names
*/
function getAllNodeWhichHaveParents (descriptor) {
var _hasChildren = {};
for (var _objName in descriptor.dynamicData) {
var _obj = descriptor.dynamicData[_objName];
_hasChildren[_obj.parent] = true;
}
return _hasChildren;
}
/**
* get all leaves
* @param {Object} hasChildren object returned by getAllNodeWhichHaveParents
* @param {Object} descriptor descriptor generated by the function splitMarkers
* @return {Array} array of leaves
*/
function getAllLeaves (hasChildren, descriptor) {
var _treeLeaves = [];
for (var _objName in descriptor.dynamicData) {
if (hasChildren[_objName] === undefined) {
var _obj = descriptor.dynamicData[_objName];
var _tempObj = {
objName : _objName,
parent : _obj.parent,
type : _obj.type,
parents : [], // TODO use _obj.parents
hasOnlyObjects : (_obj.type !== 'array') ? true : false,
branchName : ''
};
_treeLeaves.push(_tempObj);
}
}
return _treeLeaves;
}
/**
* Update treeLeaves, compute depth and unique branch name ...
* @param {Array} treeLeaves array returned by getAllLeaves
* @param {Object} descriptor descriptor generated by the function splitMarkers
*/
function computeDepthAndBranchNames (treeLeaves, descriptor) {
// for each leaf, go up in the tree until we reach the root
for (var i = 0; i < treeLeaves.length; i++) {
var _obj = treeLeaves[i];
var _objParentName = _obj.parent;
var _nbParents = 0;
var _firstParentDepth = 0;
// go up in the hierarchy until we reach the root
while (_objParentName !== '') {
_obj.parents.push(_objParentName);
var _objParent = descriptor.dynamicData[_objParentName];
if (_objParent.type === 'array') {
_obj.hasOnlyObjects = false;
if (_firstParentDepth === 0) {
_firstParentDepth = _objParent.depth;
}
}
_obj.branchName = _objParentName + ' ' +_obj.branchName; // the whitespace is the only character which cannot exist in the name of a node
_objParentName = _objParent.parent;
_nbParents++;
}
// store the depth of the leaf (= the depth of the first parent array)
if (_obj.depth === undefined ) {
_obj.depth = _firstParentDepth;
}
_obj.jsonDepth = _nbParents;
}
}
/**
* Sort function used by buildSortedHierarchy
* @param {Object} a one object of treeLeaves
* @param {Object} b one object of treeLeaves
* @return {Integer} 1, -1, 0
*/
function sortByDepth (a,b) {
// if the branch contains only objects, put it first
if (a.hasOnlyObjects === true && b.hasOnlyObjects === false) {
return -1;
}
else if (a.hasOnlyObjects === false && b.hasOnlyObjects === true) {
return 1;
}
// if the two items do not share the same branch at all, sort them by branch name.
if (a.branchName.indexOf(b.branchName) === -1 && b.branchName.indexOf(a.branchName) === -1) {
if (a.branchName > b.branchName) {
return 1;
}
else if (a.branchName < b.branchName) {
return -1;
}
}
// if we are in the same branch, sort by depth, shortest depth first
if (a.depth > b.depth) {
return 1;
}
else if (a.depth < b.depth) {
return -1;
}
// if the depth is the same, put objects before arrays
if (a.type !== 'array' && b.type === 'array') {
return -1;
}
else if (a.type === 'array' && b.type !== 'array') {
return 1;
}
if (a.jsonDepth > b.jsonDepth) {
return 1;
}
else if (a.jsonDepth < b.jsonDepth) {
return -1;
}
return 0;
}