3

I am very new to Xcode and trying - as millions - to parse a CSV file. I have read many contributions and I am managing it but I have a problem when my NSScanner intercepts an empty field: "Field_A, Field_B,, Field_D". I guess it is because it ignores empty space by default, or in this case no space at all.

String is:

"Personal","2011-01-01","Personal","Cigarettes",,4.60,"Cash","",

I tried to debug it using scanLocation:

2011-04-22 15:57:32.414 Spending[42015:a0f] Before while...scan location is:0
2011-04-22 15:57:32.414 Spending[42015:a0f] Account: "Personal" - scan location is:10
2011-04-22 15:57:32.415 Spending[42015:a0f] Date: "2011-01-01" - scan location is:23
2011-04-22 15:57:32.415 Spending[42015:a0f] Category: "Personal" - scan location is:34
2011-04-22 15:57:32.416 Spending[42015:a0f] Subcategory: "Cigarettes" - scan location is:47
2011-04-22 15:57:32.416 Spending[42015:a0f] Income: 4.600000 - scan location is:53
2011-04-22 15:57:32.416 Spending[42015:a0f] Expense: 0.000000 - scan location is:53
2011-04-22 15:57:32.417 Spending[42015:a0f] Payment: "Cash" - scan location is:60
2011-04-22 15:57:32.417 Spending[42015:a0f] Note: "" - scan location is:63

And as you can see after that even expense field gets no value (should be 4.60).

Here is the relevant piece of code:

NSScanner *scanner = [NSScanner scannerWithString:fileString];
    [scanner setCharactersToBeSkipped: [NSCharacterSet characterSetWithCharactersInString:@"\n, "]];

    NSString *account, *date, *category, *subcategory, *payment, *note;
    float income, expense;

    // Set up data delimiter using comma
    NSCharacterSet *commaSet;
    commaSet = [NSCharacterSet characterSetWithCharactersInString:@","];

    NSLog (@"Before while...scan location is:%d\n", scanner.scanLocation);

    [scanner scanUpToCharactersFromSet:commaSet intoString:&account];
    NSLog(@"Account: %@ - scan location is:%d\n",account, scanner.scanLocation);

    [scanner scanUpToCharactersFromSet:commaSet intoString:&date];
    NSLog(@"Date: %@ - scan location is:%d\n",date, scanner.scanLocation);

    [scanner scanUpToCharactersFromSet:commaSet intoString:&category]; 
    NSLog(@"Category: %@ - scan location is:%d\n",category, scanner.scanLocation);

    [scanner scanUpToCharactersFromSet:commaSet intoString:&subcategory]; 
    NSLog(@"Subcategory: %@ - scan location is:%d\n",subcategory, scanner.scanLocation);

    [scanner scanFloat:&income];
    NSLog(@"Income: %f - scan location is:%d\n",income, scanner.scanLocation);

    [scanner scanFloat:&expense]; 
    NSLog(@"Expense: %f - scan location is:%d\n",expense, scanner.scanLocation);

    [scanner scanUpToCharactersFromSet:commaSet intoString:&payment]; 
    NSLog(@"Payment: %@ - scan location is:%d\n",payment, scanner.scanLocation);

    [scanner scanUpToCharactersFromSet:commaSet intoString:&note];
    NSLog(@"Note: %@\n - scan location is:%d",note, scanner.scanLocation);

I tried looking carefully through NSScanner Class Reference, but could not get an idea? Do you have any?

Thanks, Fabrizio.

Fabrizio Prosperi
  • 1,398
  • 4
  • 18
  • 32

3 Answers3

2

Parsing CSV in Objective-C? That sounds familiar:

https://github.com/davedelong/CHCSVParser

Disclaimer: I wrote it. :)


For what you're doing, you could just take the file and run in through something like the +[NSArray arrayWithContentsOfCSVFile:encoding:error:] method, or you could read it into a string and do something like this:

NSString *csv = @"\"Personal\",\"2011-01-01\",\"Personal\",\"Cigarettes\",,4.60,\"Cash\",\"\",";
NSLog(@"%@", [csv CSVComponents]);

Which logs:

2011-04-22 09:51:16.651 CHCSVParser[2658:903] (
        (
        Personal,
        "2011-01-01",
        Personal,
        Cigarettes,
        "",
        "4.60",
        Cash,
        ""
    )
)

(Note that this is an NSArray of NSArrays of NSStrings)

If you're concerned about memory buildup, then you could also use a CHCSVParser directly and receive information via a delegate. It operates pretty much identically to how NSXMLParser works.

Dave DeLong
  • 242,470
  • 58
  • 448
  • 498
  • BTW: This is also a great solution that I have used. – dawg Apr 22 '11 at 18:35
  • Hi Dave, thank you for your suggestion regarding arrayWithContentsOfCSVFile, I will have a look at it. Regarding the code you posted there is only one issue: I can't edit my string adding backslashes. Anyway, I will have a look at your code and maybe finding the hints I need. – Fabrizio Prosperi Apr 24 '11 at 10:42
  • Thank you Dave, I have tried your code and it works smoothly. I have to admit I preferred to solve my issue just with objective-c solution without the add of other code but your class will be very helpful also in writing, therefore thanks! – Fabrizio Prosperi Apr 24 '11 at 17:45
  • @Fabrizio the backslashes are just there to get the string to compile correctly. They're not actually present in the final string (`\"` is how you get `"` to show up in a string) – Dave DeLong Apr 24 '11 at 18:11
  • Yep, I had the chance to notice also that Dave, and using arrayWithContentsOfCSVString did exactly what I needed, recognizing the empty field as well...now I have tons of other issues for the rest of the application deployment (what a newbie!)...I will surely need other help soon :) Thank you so much. – Fabrizio Prosperi Apr 25 '11 at 10:17
0

The reason why the scanner doesn't see your empty field is because you told it to skip commas. You called setCharactersToBeSkipped with a set of 3 characters:

  1. '\n' The newline character
  2. ',' Comma
  3. ' ' Space

When you then ask the scanner to "scanFloat", it goes over any skippable character until it reaches a decimal number. This is how the empty field gets skipped.

If you want to catch empty fields, remove the comma from your set of characters to skip. Then, any time a scan function find an empty field, it will return NO. You might have to increment the scanning position manually when this happens.

bineteri
  • 721
  • 1
  • 5
  • 12
0

Take a look at this article on CSV Scanners.

Here is another article

Dave DeLong's solution also works great.

Bottom line: CSV seems trivial, but it really is not if you are wanting to handle any CSV thrown at you gracefully.

dawg
  • 98,345
  • 23
  • 131
  • 206