Split a string by commas but ignore commas within double-quotes using Javascript

Viewed 95202

I'm looking for [a, b, c, "d, e, f", g, h]to turn into an array of 6 elements: a, b, c, "d,e,f", g, h. I'm trying to do this through Javascript. This is what I have so far:

str = str.split(/,+|"[^"]+"/g); 

But right now it's splitting out everything that's in the double-quotes, which is incorrect.

Edit: Okay sorry I worded this question really poorly. I'm being given a string not an array.

var str = 'a, b, c, "d, e, f", g, h';

And I want to turn that into an array using something like the "split" function.

17 Answers

regex: /,(?=(?:(?:[^"]*"){2})*[^"]*$)/

enter image description here

const input_line = '"2C95699FFC68","201 S BOULEVARDRICHMOND, VA 23220","8299600062754882","2018-09-23"'

let my_split = input_line.split(/,(?=(?:(?:[^"]*"){2})*[^"]*$)/)[4]

Output: 
my_split[0]: "2C95699FFC68", 
my_split[1]: "201 S BOULEVARDRICHMOND, VA 23220", 
my_split[2]: "8299600062754882", 
my_split[3]: "2018-09-23"

Reference following link for an explanation: regexr.com/44u6o

Here's a non-regex one that assumes doublequotes will come in pairs:

function splitCsv(str) {
  return str.split(',').reduce((accum,curr)=>{
    if(accum.isConcatting) {
      accum.soFar[accum.soFar.length-1] += ','+curr
    } else {
      accum.soFar.push(curr)
    }
    if(curr.split('"').length % 2 == 0) {
      accum.isConcatting= !accum.isConcatting
    }
    return accum;
  },{soFar:[],isConcatting:false}).soFar
}

console.log(splitCsv('asdf,"a,d",fdsa'),' should be ',['asdf','"a,d"','fdsa'])
console.log(splitCsv(',asdf,,fds,'),' should be ',['','asdf','','fds',''])
console.log(splitCsv('asdf,"a,,,d",fdsa'),' should be ',['asdf','"a,,,d"','fdsa'])

Here's the regex we're using to extract valid arguments from a comma-separated argument list, supporting double-quoted arguments. It works for the outlined edge cases. E.g.

  • doesn't include quotes in the matches
  • works with white spaces in matches
  • works with empty fields

(?<=")[^"]+?(?="(?:\s*?,|\s*?$))|(?<=(?:^|,)\s*?)(?:[^,"\s][^,"]*[^,"\s])|(?:[^,"\s])(?![^"]*?"(?:\s*?,|\s*?$))(?=\s*?(?:,|$))

Proof: https://regex101.com/r/UL8kyy/3/tests (Note: currently only works in Chrome because the regex uses lookbehinds which are only supported in ECMA2018)

According to our guidelines it avoids non-capturing groups and greedy matching.

I'm sure it can be simplified, I'm open to suggestions / additional test cases.

For anyone interested, the first part matches double-quoted, comma-delimited arguments:

(?<=")[^"]+?(?="(?:\s*?,|\s*?$))

And the second part matches comma-delimited arguments by themselves:

(?<=(?:^|,)\s*?)(?:[^,"\s][^,"]*[^,"\s])|(?:[^,"\s])(?![^"]*?"(?:\s*?,|\s*?$))(?=\s*?(?:,|$))

I almost liked the accepted answer, but it didn't parse the space correctly, and/or it left the double quotes untrimmed, so here is my function:

    /**
     * Splits the given string into components, and returns the components array.
     * Each component must be separated by a comma.
     * If the component contains one or more comma(s), it must be wrapped with double quotes.
     * The double quote must not be used inside components (replace it with a special string like __double__quotes__ for instance, then transform it again into double quotes later...).
     *
     * https://stackoverflow.com/questions/11456850/split-a-string-by-commas-but-ignore-commas-within-double-quotes-using-javascript
     */
    function splitComponentsByComma(str){
        var ret = [];
        var arr = str.match(/(".*?"|[^",]+)(?=\s*,|\s*$)/g);
        for (let i in arr) {
            let element = arr[i];
            if ('"' === element[0]) {
                element = element.substr(1, element.length - 2);
            } else {
                element = arr[i].trim();
            }
            ret.push(element);
        }
        return ret;
    }
    console.log(splitComponentsByComma('Hello World, b, c, "d, e, f", c')); // [ 'Hello World', 'b', 'c', 'd, e, f', 'c' ]

Parse any CSV or CSV-String code based on TYPESCRIPT

public parseCSV(content:string):any[string]{
        return content.split("\n").map(ar=>ar.split(/,(?=(?:(?:[^"]*"){2})*[^"]*$)/).map(refi=>refi.replace(/[\x00-\x08\x0E-\x1F\x7F-\uFFFF]/g, "").trim()));
    }

var str='"abc",jkl,1000,qwerty6000';

parseCSV(str);

output :

[
"abc","jkl","1000","qwerty6000"
]

This takes a csv file one line at a time and spits back an array with commas inside speech marks intact. if there are no speech marks detected it just .split(",")s as normal... could probs replace that second loop with something but it does the job as is

function parseCSVLine(str){
    if(str.indexOf("\"")>-1){
        var aInputSplit = str.split(",");
        var aOutput = [];
        var iMatch = 0;
        //var adding = 0;
        for(var i=0;i<aInputSplit.length;i++){
            if(aInputSplit[i].indexOf("\"")>-1){
                var sWithCommas = aInputSplit[i];
                for(var z=i;z<aInputSplit.length;z++){
                    if(z !== i && aInputSplit[z].indexOf("\"") === -1){
                        sWithCommas+= ","+aInputSplit[z];
                    }else if(z !== i && aInputSplit[z].indexOf("\"") > -1){
                        sWithCommas+= ","+aInputSplit[z];
                        sWithCommas.replace(new RegExp("\"", 'g'), "");
                        aOutput.push(sWithCommas);
                        i=z;
                        z=aInputSplit.length+1;
                        iMatch++;
                    }
                    if(z === aInputSplit.length-1){
                        if(iMatch === 0){
                            aOutput.push(aInputSplit[z]);
                        }                  
                        iMatch = 0;
                    }
                }
            }else{
                aOutput.push(aInputSplit[i]);
            }
        }
        return aOutput
    }else{
        return str.split(",")
    }
}

jsfiddle setting image code output image

The code works if your input string in the format of stringTocompare. Run the code on https://jsfiddle.net/ to see output for fiddlejs setting. Please refer to the screenshot. You can either use split function for the same for the code below it and tweak the code according to you need. Remove the bold or word with in ** from the code if you dont want to have comma after split attach=attach**+","**+actualString[t+1].

var stringTocompare='"Manufacturer","12345","6001","00",,"Calfe,eto,lin","Calfe,edin","4","20","10","07/01/2018","01/01/2006",,,,,,,,"03/31/2004"';

console.log(stringTocompare);

var actualString=stringTocompare.split(',');
console.log("Before");
for(var i=0;i<actualString.length;i++){
console.log(actualString[i]);
}
//var actualString=stringTocompare.split(/,(?=(?:(?:[^"]*"){2})*[^"]*$)/);
for(var i=0;i<actualString.length;i++){
var flag=0;
var x=actualString[i];
if(x!==null)
{
if(x[0]=='"' && x[x.length-1]!=='"'){
   var p=0;
   var t=i;
   var b=i;
   for(var k=i;k<actualString.length;k++){
   var y=actualString[k];
        if(y[y.length-1]!=='"'){        
        p++;
        }
        if(y[y.length-1]=='"'){

                flag=1;
        }
        if(flag==1)
        break;
   }
   var attach=actualString[t];
for(var s=p;s>0;s--){

  attach=attach+","+actualString[t+1];
  t++;
}
actualString[i]=attach;
actualString.splice(b+1,p);
}
}


}
console.log("After");
for(var i=0;i<actualString.length;i++){
console.log(actualString[i]);
}




  [1]: https://i.stack.imgur.com/3FcxM.png

I solved this with a simple parser.

It simply goes through the string char by char, splitting off a segment when it finds the split_char (e.g. comma), but also has an on/off flag which is switched by finding the encapsulator_char (e.g. quote). It doesn't require the encapsulator to be at the start of the field/segment (a,b","c,d would produce 3 segments, with 'b","c' as the second), but it should work for a well formed CSV with escaped encapsulator chars.

function split_except_within(text, split_char, encapsulator_char, escape_char) {
    var start = 0
    var encapsulated = false
    var fields = []
    for (var c = 0; c < text.length; c++) {
        var char = text[c]
        if (char === split_char && ! encapsulated) {
            fields.push(text.substring(start, c))
            start = c+1
        }
        if (char === encapsulator_char && (c === 0 || text[c-1] !== escape_char) )             
            encapsulated = ! encapsulated
    }
    fields.push(text.substring(start))
    return fields
}

https://jsfiddle.net/7hty8Lvr/1/

const csvSplit = (line) => {
    let splitLine = [];

    var quotesplit = line.split('"');
    var lastindex = quotesplit.length - 1;
    // split evens removing outside quotes, push odds
    quotesplit.forEach((val, index) => {
        if (index % 2 === 0) {
            var firstchar = (index == 0) ? 0 : 1;
            var trimmed = (index == lastindex) 
                ? val.substring(firstchar)
                : val.slice(firstchar, -1);
            trimmed.split(",").forEach(v => splitLine.push(v));
        } else {
            splitLine.push(val);
        }
    });
    return splitLine;
}

this works as long as quotes always come on the outside of values that contain the commas that need to be excluded (i.e. a csv file).

if you have stuff like '1,2,4"2,6",8' it will not work.

Related