Link to home
Start Free TrialLog in
Avatar of nap0leon
nap0leon

asked on

JavaScript/jQuery comma separated lists to array

I inherited a library that supports setting values for variables inside the library like this:
myLibrary.set('var1','var1_val')

Open in new window

A request has come along to update the .set() method so that it can support setting multiple variable-value pairs, like this:
myLibrary.set('var1,var2','var1_val,var2_val')

Open in new window

Easy enough... just split the "variables" and the "values" into arrays...

BUT sometimes a value is actually a comma-separated list of values, like this:
myLibrary.set('var1','var1_val1,var1_val2')

Open in new window

Which means the multiple variable-value pairs could look like this:
myLibrary.set('var1,var2','"var1_val1,var1_val2",var2_val1')

Open in new window

So splitting the values section on comma just got a bit more complicated...

Also, because different developers have different coding styles, the quotes in that line could be reversed, like this:
myLibrary.set("var1,var2","'var1_val1,var1_val2',var2_val1")

Open in new window

or even inconsistent between the variable list and the values lists, like this (double-quotes are the variable list but single-quotes around the values list):
myLibrary.set("var1,var2",'"var1_val1,var1_val2",var2_val1')

Open in new window

I have a function that seems to work, but I am not positive that it is 100%.

Here is the snippet of the .set() method where this new function is used to turn the variables and values into an array so that I can update the correct item in the JS database:
	variables = myLibrary.parseCommaList(variableList);
	values = myLibrary.parseCommaList(valueList);
	if (variables.length !== values.length) {
		throw myLibrary.error('', 'There must be an equal number of variables and values. # Variables = ' + variables.length + ', # Values = ' + values.length);
	}
	l = variables.length;
	for (i = 0; i < l; i++) {
		database[variables[i]] = values[i];			// use for obfuscated access
	}

Open in new window

For simplicity of seeking guidance, I've converted the method into a function:
function parseCommaList(list) {
	"use strict";
	var i = 0,				
		returnArray = [],
		j = 0,
		l = 0,
		quoteDelimiter = '',
		oldQuoteDelimiter = '',
		char = '';				
	if (typeof list !== 'string') {
		throw myLibrary.error('', 'Argument not a string in parseCommaList');
	}
	l = list.length;
	while (j < l) {
		char = list.substr(j,1);
		if (char === "'" || char === '"') {
			if (quoteDelimiter === char) {
				oldQuoteDelimiter = quoteDelimiter;
				quoteDelimiter = "";
				j ++;
			} else {
				oldQuoteDelimiter = "";
				quoteDelimiter = char;
				j ++;
				i = j;
			}
		} else if(char === ',') {
			if (quoteDelimiter === '') {
				returnArray.push(list.substring(i, j).replace(oldQuoteDelimiter, ''));
				i = j + 1;
			}
			j ++;
		} else {
			j ++;
		}
	}
	if (i != j) {
		returnArray.push(list.substring(i).replace("'","").replace('"',""));
	}
	return returnArray;	
};

Open in new window

Which can be tested like this:
For testing purposes, I'll use this to verify the values alerted are correct:
[code]
	var myList = '"a,b,c",d'
	var values = parseCommaList(myList)
	var l = values.length
	for (i = 0; i < l; i++) {
		alert(values[i]);
	}	

Open in new window


This version has an issue where the trailing quote is not removed, so I added a hack at the end of the function to remove any lingering quotes (line 38 above - the two replace()s).
e.g.,
For this line:
myLibrary.set('var1','a,b,c')

Open in new window

The function returns
a,b,c'

Open in new window

If anyone has an approach to doing this that works 100%, I'd love to see it.  The solution can use any combination of JavaScript and jQuery - as long as the function returns an array of items so I can perform my length verification and loop through them to assign values in the database.

A couple simple tests that the function should be able to pass:
list = "a,b,c"
list = '"a,b,c",d'
list = '"a,b,c","d"'
list = "'a,b,c',d"
list = "'a,b,c','d'"
list = "d,'a,b,c'"
list = "'d','a,b,c'"
list = 'd,"a,b,c"'
list = '"d","a,b,c"'

and one with a couple single-value values and a couple list-values:
list = "a,'b,c,d',e,'f,g,h'"
Avatar of Jon Norman
Jon Norman
Flag of United Kingdom of Great Britain and Northern Ireland image

You are much better off supporting the passing of either

a single object
a string as the first element and a string or array as the second element
an array as the first element and array as the second element with the same number of values (the values in the second array could be another array)

That way values (and possibly variable names) could actually contain a comma.

You can then check what type you get - isArray(obj), or typeof(obj).

For an object you would be able to pass
{
  var1:["var1_val1","var1_val2"],
  var2:"var2_val1"
}
For an array you would be able to pass:
["var1","var2"] as the first param, and [["var1_val1","var1_val2"],"var2_val1"] as the second param.

At least that way you'd know for definite what they are trying to pass to the function.
Avatar of nap0leon
nap0leon

ASKER

I agree - but this library is used by over 1000 users, mostly contractors, spread out across the world.  Some of the people who touch use it are not even developers - they may be using the library when they use their CMS system to add "onclick" events to HTML elements.

So... it needs to support n00bies and non-techies who might be asked to do stuff that only technical types should be doing.  From a library user's perspective, they are passing in strings, some of these strings happen to be comma separated lists.  In some cases they may merely be passing in JS variable names that are defined elsewhere and they have no clue what that variable actually contains.
Hmmm, ok, I think a couple of regular expressions may help you here, I will have a go at that, but it might take me a couple of mos which I haven't got right now. Will hope to look at this tomorrow.
Hi nap0leon,

I have come up with the following using regular expressions:

function parseCommaList(list) {
    regExp = /((?:\"[\s\S]*?\")|(?:'[\s\S]*?'))|(?:\,)/;
    returnArray = new Array();
    splitArray = list.split(regExp);
    regExp = /(?:^(\s*)\"([\s\S]*)\"(\s*)$)|(?:^(\s*)'([\s\S]*)'(\s*)$)/;
    for (var i = 0; i < splitArray.length; i++) {
        if (splitArray[i] != undefined) {
            if (splitArray[i] !== "" || i <= 0 || splitArray[i - 1] == undefined || !(splitArray[i - 1].match(regExp))) {
                if (splitArray[i] !== "" || i >= splitArray.length || splitArray[i + 1] == undefined || !(splitArray[i + 1].match(regExp))) {
                    returnArray.push(splitArray[i].replace(regExp, "$1$2$3$4$5$6"));
                }
            }
        }
    }
    return returnArray;
}

Open in new window

it seems to work for all the conceivable values that I've come up with, it does the same as yours with all values except for a single "," (mine returns an array two empty strings) and doesn't deal with mismatched quotes the same, yours assumes the end quote, mine doesn't.

you can play with http://jsfiddle.net/TP8Xg/ to see if this is any use.
Works for the most complex well-formed examples I could think of.

This might be a bit much to ask... but can you make it throw an error if it encounters an opening quote that does not have a closing quote?

e.g., var myList = "a,'b,c,d"
var values = parseCommaList(myList)

Not a big deal... it currently returns an array with length 4 instead of 2 which will cause an error when comparing the length of the "variables list" with the length of the "values list".

If it's easy, great, if not, then what you have above is excellent.
ASKER CERTIFIED SOLUTION
Avatar of Jon Norman
Jon Norman
Flag of United Kingdom of Great Britain and Northern Ireland image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Excellent solution (and yet another indication that I need to buck up and learn to write regular expressions)
JonNorman - found a bug in this (does not handle lists correctly in IE) - can you take a look at it for me?
https://www.experts-exchange.com/questions/27754499/regular-expression-broken-in-IE-works-in-FireFox-and-Chrome.html