JavaScript/jQuery comma separated lists to array

nap0leon
nap0leon used Ask the Experts™
on
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'"
Comment
Watch Question

Do more with

Expert Office
EXPERT OFFICE® is a registered trademark of EXPERTS EXCHANGE®
Jon NormanInformation Systems Manager
Top Expert 2012

Commented:
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.
Top Expert 2011

Author

Commented:
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.
Jon NormanInformation Systems Manager
Top Expert 2012

Commented:
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.
Success in ‘20 With a Profitable Pricing Strategy

Do you wonder if your IT business is truly profitable or if you should raise your prices? Learn how to calculate your overhead burden using our free interactive tool and use it to determine the right price for your IT services. Start calculating Now!

Jon NormanInformation Systems Manager
Top Expert 2012

Commented:
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.
Top Expert 2011

Author

Commented:
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.
Information Systems Manager
Top Expert 2012
Commented:
This is fairly easy it counts the matches and works out the remainder when divided by 2:

I've updated the fiddle - http://jsfiddle.net/TP8Xg/2/

I've left you to put the two errors in (one for mismatched single quotes, one for double).

function parseCommaList(list) {
    regExp = /((?:\"[\s\S]*?\")|(?:'[\s\S]*?'))|(?:\,)/;
    if (list.match(/\"/g) && list.match(/\"/g).length%2===1){
        alert("error mismatched double quotes on: >"+list+"<");
    }
    if (list.match(/'/g) && list.match(/'/g).length%2===1){
        alert("error mismatched single quotes on: >"+list+"<");
    }
    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

Top Expert 2011

Author

Commented:
Excellent solution (and yet another indication that I need to buck up and learn to write regular expressions)
Top Expert 2011

Author

Commented:
JonNorman - found a bug in this (does not handle lists correctly in IE) - can you take a look at it for me?
http://www.experts-exchange.com/Programming/Languages/Scripting/JavaScript/Q_27754499.html

Do more with

Expert Office
Submit tech questions to Ask the Experts™ at any time to receive solutions, advice, and new ideas from leading industry professionals.

Start 7-Day Free Trial