manyfest
Version:
JSON Object Manifest for Data Description and Parsing
298 lines (260 loc) • 9.46 kB
JavaScript
// TODO: This is an inelegant solution to delay the rewrite of Manyfest.
// Fable 3.0 has a service for data formatting that deals well with nested enclosures.
// The Manyfest library predates fable 3.0 and the services structure of it, so the functions
// are more or less pure javascript and as functional as they can be made to be.
// Until we shift Manyfest to be a fable service, these three functions were pulled out of
// fable to aid in parsing functions with nested enclosures.
module.exports = {
/**
* Count the number of segments in a string, respecting enclosures
*
* @param {string} pString
* @param {string} pSeparator
* @param {object} pEnclosureStartSymbolMap
* @param {object} pEnclosureEndSymbolMap
* @returns the count of segments in the string as a number
*/
stringCountSegments: (pString, pSeparator, pEnclosureStartSymbolMap, pEnclosureEndSymbolMap) =>
{
let tmpString = (typeof(pString) == 'string') ? pString : '';
let tmpSeparator = (typeof(pSeparator) == 'string') ? pSeparator : '.';
let tmpEnclosureStartSymbolMap = (typeof(pEnclosureStartSymbolMap) == 'object') ? pEnclosureStart : { '{': 0, '[': 1, '(': 2 };
let tmpEnclosureEndSymbolMap = (typeof(pEnclosureEndSymbolMap) == 'object') ? pEnclosureEnd : { '}': 0, ']': 1, ')': 2 };
if (pString.length < 1)
{
return 0;
}
let tmpSegmentCount = 1;
let tmpEnclosureStack = [];
for (let i = 0; i < tmpString.length; i++)
{
// IF This is the start of a segment
if ((tmpString[i] == tmpSeparator)
// AND we are not in a nested portion of the string
&& (tmpEnclosureStack.length == 0))
{
// Increment the segment count
tmpSegmentCount++;
}
// IF This is the start of an enclosure
else if (tmpString[i] in tmpEnclosureStartSymbolMap)
{
// Add it to the stack!
tmpEnclosureStack.push(tmpEnclosureStartSymbolMap[tmpString[i]]);
}
// IF This is the end of an enclosure
else if ((tmpString[i] in tmpEnclosureEndSymbolMap)
// AND it matches the current nest level symbol
&& tmpEnclosureEndSymbolMap[tmpString[i]] == tmpEnclosureStack[tmpEnclosureStack.length - 1])
{
// Pop it off the stack!
tmpEnclosureStack.pop();
}
}
return tmpSegmentCount;
},
/**
* Get the first segment in a string, respecting enclosures
*
* @param {string} pString
* @param {string} pSeparator
* @param {object} pEnclosureStartSymbolMap
* @param {object} pEnclosureEndSymbolMap
* @returns the first segment in the string as a string
*/
stringGetFirstSegment: (pString, pSeparator, pEnclosureStartSymbolMap, pEnclosureEndSymbolMap) =>
{
let tmpString = (typeof(pString) == 'string') ? pString : '';
let tmpSeparator = (typeof(pSeparator) == 'string') ? pSeparator : '.';
let tmpEnclosureStartSymbolMap = (typeof(pEnclosureStartSymbolMap) == 'object') ? pEnclosureStart : { '{': 0, '[': 1, '(': 2 };
let tmpEnclosureEndSymbolMap = (typeof(pEnclosureEndSymbolMap) == 'object') ? pEnclosureEnd : { '}': 0, ']': 1, ')': 2 };
if (pString.length < 1)
{
return 0;
}
let tmpEnclosureStack = [];
for (let i = 0; i < tmpString.length; i++)
{
// IF This is the start of a segment
if ((tmpString[i] == tmpSeparator)
// AND we are not in a nested portion of the string
&& (tmpEnclosureStack.length == 0))
{
// Return the segment
return tmpString.substring(0, i);
}
// IF This is the start of an enclosure
else if (tmpString[i] in tmpEnclosureStartSymbolMap)
{
// Add it to the stack!
tmpEnclosureStack.push(tmpEnclosureStartSymbolMap[tmpString[i]]);
}
// IF This is the end of an enclosure
else if ((tmpString[i] in tmpEnclosureEndSymbolMap)
// AND it matches the current nest level symbol
&& tmpEnclosureEndSymbolMap[tmpString[i]] == tmpEnclosureStack[tmpEnclosureStack.length - 1])
{
// Pop it off the stack!
tmpEnclosureStack.pop();
}
}
return tmpString;
},
/**
* Get all segments in a string, respecting enclosures
*
* @param {string} pString
* @param {string} pSeparator
* @param {object} pEnclosureStartSymbolMap
* @param {object} pEnclosureEndSymbolMap
* @returns the first segment in the string as a string
*/
stringGetSegments: (pString, pSeparator, pEnclosureStartSymbolMap, pEnclosureEndSymbolMap)=>
{
let tmpString = (typeof(pString) == 'string') ? pString : '';
let tmpSeparator = (typeof(pSeparator) == 'string') ? pSeparator : '.';
let tmpEnclosureStartSymbolMap = (typeof(pEnclosureStartSymbolMap) == 'object') ? pEnclosureStart : { '{': 0, '[': 1, '(': 2 };
let tmpEnclosureEndSymbolMap = (typeof(pEnclosureEndSymbolMap) == 'object') ? pEnclosureEnd : { '}': 0, ']': 1, ')': 2 };
let tmpCurrentSegmentStart = 0;
let tmpSegmentList = [];
if (pString.length < 1)
{
return tmpSegmentList;
}
let tmpEnclosureStack = [];
for (let i = 0; i < tmpString.length; i++)
{
// IF This is the start of a segment
if ((tmpString[i] == tmpSeparator)
// AND we are not in a nested portion of the string
&& (tmpEnclosureStack.length == 0))
{
// Return the segment
tmpSegmentList.push(tmpString.substring(tmpCurrentSegmentStart, i));
tmpCurrentSegmentStart = i+1;
}
// IF This is the start of an enclosure
else if (tmpString[i] in tmpEnclosureStartSymbolMap)
{
// Add it to the stack!
tmpEnclosureStack.push(tmpEnclosureStartSymbolMap[tmpString[i]]);
}
// IF This is the end of an enclosure
else if ((tmpString[i] in tmpEnclosureEndSymbolMap)
// AND it matches the current nest level symbol
&& tmpEnclosureEndSymbolMap[tmpString[i]] == tmpEnclosureStack[tmpEnclosureStack.length - 1])
{
// Pop it off the stack!
tmpEnclosureStack.pop();
}
}
if (tmpCurrentSegmentStart < tmpString.length)
{
tmpSegmentList.push(tmpString.substring(tmpCurrentSegmentStart));
}
return tmpSegmentList;
},
/**
* Count the number of enclosures in a string based on the start and end characters.
*
* If no start or end characters are specified, it will default to parentheses. If the string is not a string, it will return 0.
*
* @param {string} pString
* @param {string} pEnclosureStart
* @param {string} pEnclosureEnd
* @returns the count of full in the string
*/
stringCountEnclosures: (pString, pEnclosureStart, pEnclosureEnd) =>
{
let tmpString = (typeof(pString) == 'string') ? pString : '';
let tmpEnclosureStart = (typeof(pEnclosureStart) == 'string') ? pEnclosureStart : '(';
let tmpEnclosureEnd = (typeof(pEnclosureEnd) == 'string') ? pEnclosureEnd : ')';
let tmpEnclosureCount = 0;
let tmpEnclosureDepth = 0;
for (let i = 0; i < tmpString.length; i++)
{
// This is the start of an enclosure
if (tmpString[i] == tmpEnclosureStart)
{
if (tmpEnclosureDepth == 0)
{
tmpEnclosureCount++;
}
tmpEnclosureDepth++;
}
else if (tmpString[i] == tmpEnclosureEnd)
{
tmpEnclosureDepth--;
}
}
return tmpEnclosureCount;
},
/**
* Get the value of the enclosure at the specified index.
*
* If the index is not a number, it will default to 0. If the string is not a string, it will return an empty string. If the enclosure is not found, it will return an empty string. If the enclosure
*
* @param {string} pString
* @param {number} pEnclosureIndexToGet
* @param {string} pEnclosureStart
* @param {string}} pEnclosureEnd
* @returns {string}
*/
stringGetEnclosureValueByIndex: (pString, pEnclosureIndexToGet, pEnclosureStart, pEnclosureEnd) =>
{
let tmpString = (typeof(pString) == 'string') ? pString : '';
let tmpEnclosureIndexToGet = (typeof(pEnclosureIndexToGet) == 'number') ? pEnclosureIndexToGet : 0;
let tmpEnclosureStart = (typeof(pEnclosureStart) == 'string') ? pEnclosureStart : '(';
let tmpEnclosureEnd = (typeof(pEnclosureEnd) == 'string') ? pEnclosureEnd : ')';
let tmpEnclosureCount = 0;
let tmpEnclosureDepth = 0;
let tmpMatchedEnclosureIndex = false;
let tmpEnclosedValueStartIndex = 0;
let tmpEnclosedValueEndIndex = 0;
for (let i = 0; i < tmpString.length; i++)
{
// This is the start of an enclosure
if (tmpString[i] == tmpEnclosureStart)
{
tmpEnclosureDepth++;
// Only count enclosures at depth 1, but still this parses both pairs of all of them.
if (tmpEnclosureDepth == 1)
{
tmpEnclosureCount++;
if (tmpEnclosureIndexToGet == (tmpEnclosureCount - 1))
{
// This is the start of *the* enclosure
tmpMatchedEnclosureIndex = true;
tmpEnclosedValueStartIndex = i;
}
}
}
// This is the end of an enclosure
else if (tmpString[i] == tmpEnclosureEnd)
{
tmpEnclosureDepth--;
// Again, only count enclosures at depth 1, but still this parses both pairs of all of them.
if ((tmpEnclosureDepth == 0) &&
tmpMatchedEnclosureIndex &&
(tmpEnclosedValueEndIndex <= tmpEnclosedValueStartIndex))
{
tmpEnclosedValueEndIndex = i;
tmpMatchedEnclosureIndex = false;
}
}
}
if (tmpEnclosureCount <= tmpEnclosureIndexToGet)
{
// Return an empty string if the enclosure is not found
return '';
}
if ((tmpEnclosedValueEndIndex > 0) && (tmpEnclosedValueEndIndex > tmpEnclosedValueStartIndex))
{
return tmpString.substring(tmpEnclosedValueStartIndex+1, tmpEnclosedValueEndIndex);
}
else
{
return tmpString.substring(tmpEnclosedValueStartIndex+1);
}
}
}