blob: 6b8d0b4067f6158b6e750a6adaa5a247815202d9 [file] [log] [blame]
import csv
import cffi
# IN-PROGRESS. See the demo at the end of the file
def _make_ffi_from_dialect(dialect_name):
dialect = csv.get_dialect(dialect_name)
ffi = cffi.FFI()
ffi.cdef("""
long parse_line(char *rawline, long inputlength);
""")
d = {'quotechar': ord(dialect.quotechar),
'quoting': int(dialect.quoting),
'skipinitialspace': int(dialect.skipinitialspace),
'delimiter': ord(dialect.delimiter),
'doublequote': int(dialect.doublequote),
'strict': int(dialect.strict),
}
if dialect.escapechar is not None:
d['is_escape_char'] = '== %d' % ord(dialect.escapechar)
else:
d['is_escape_char'] = '&& 0'
ffi.set_source('_fastcsv_' + dialect_name, r'''
typedef enum {
START_RECORD, START_FIELD, ESCAPED_CHAR, IN_FIELD,
IN_QUOTED_FIELD, ESCAPE_IN_QUOTED_FIELD, QUOTE_IN_QUOTED_FIELD,
EAT_CRNL
} ParserState;
typedef enum {
QUOTE_MINIMAL, QUOTE_ALL, QUOTE_NONNUMERIC, QUOTE_NONE
} QuoteStyle;
typedef struct {
ParserState state; /* current CSV parse state */
char *field; /* build current field in here */
int field_size; /* size of allocated buffer */
int field_len; /* length of current field */
int numeric_field; /* treat field as numeric */
} ReaderObj;
static void
parse_add_char(ReaderObj *self, char c)
{
*self->field++ = c;
}
static void
parse_save_field(ReaderObj *self)
{
*self->field++ = 0;
}
static int
parse_process_char(ReaderObj *self, char c)
{
switch (self->state) {
case START_RECORD:
/* start of record */
if (c == '\0')
/* empty line - return [] */
break;
else if (c == '\n' || c == '\r') {
self->state = EAT_CRNL;
break;
}
/* normal character - handle as START_FIELD */
self->state = START_FIELD;
/* fallthru */
case START_FIELD:
/* expecting field */
if (c == '\n' || c == '\r' || c == '\0') {
/* save empty field - return [fields] */
parse_save_field(self);
self->state = (c == '\0' ? START_RECORD : EAT_CRNL);
}
else if (c == %(quotechar)d &&
%(quoting)d != QUOTE_NONE) {
/* start quoted field */
self->state = IN_QUOTED_FIELD;
}
else if (c %(is_escape_char)s) {
/* possible escaped character */
self->state = ESCAPED_CHAR;
}
else if (c == ' ' && %(skipinitialspace)d)
/* ignore space at start of field */
;
else if (c == %(delimiter)d) {
/* save empty field */
parse_save_field(self);
}
else {
/* begin new unquoted field */
if (%(quoting)d == QUOTE_NONNUMERIC)
self->numeric_field = 1;
parse_add_char(self, c);
self->state = IN_FIELD;
}
break;
case ESCAPED_CHAR:
if (c == '\0')
c = '\n';
parse_add_char(self, c);
self->state = IN_FIELD;
break;
case IN_FIELD:
/* in unquoted field */
if (c == '\n' || c == '\r' || c == '\0') {
/* end of line - return [fields] */
parse_save_field(self);
self->state = (c == '\0' ? START_RECORD : EAT_CRNL);
}
else if (c %(is_escape_char)s) {
/* possible escaped character */
self->state = ESCAPED_CHAR;
}
else if (c == %(delimiter)d) {
/* save field - wait for new field */
parse_save_field(self);
self->state = START_FIELD;
}
else {
/* normal character - save in field */
parse_add_char(self, c);
}
break;
case IN_QUOTED_FIELD:
/* in quoted field */
if (c == '\0')
;
else if (c %(is_escape_char)s) {
/* Possible escape character */
self->state = ESCAPE_IN_QUOTED_FIELD;
}
else if (c == %(quotechar)d &&
%(quoting)d != QUOTE_NONE) {
if (%(doublequote)d) {
/* doublequote; " represented by "" */
self->state = QUOTE_IN_QUOTED_FIELD;
}
else {
/* end of quote part of field */
self->state = IN_FIELD;
}
}
else {
/* normal character - save in field */
parse_add_char(self, c);
}
break;
case ESCAPE_IN_QUOTED_FIELD:
if (c == '\0')
c = '\n';
parse_add_char(self, c);
self->state = IN_QUOTED_FIELD;
break;
case QUOTE_IN_QUOTED_FIELD:
/* doublequote - seen a quote in an quoted field */
if (%(quoting)d != QUOTE_NONE &&
c == %(quotechar)d) {
/* save "" as " */
parse_add_char(self, c);
self->state = IN_QUOTED_FIELD;
}
else if (c == %(delimiter)d) {
/* save field - wait for new field */
parse_save_field(self);
self->state = START_FIELD;
}
else if (c == '\n' || c == '\r' || c == '\0') {
/* end of line - return [fields] */
parse_save_field(self);
self->state = (c == '\0' ? START_RECORD : EAT_CRNL);
}
else if (!%(strict)d) {
parse_add_char(self, c);
self->state = IN_FIELD;
}
else {
/* illegal */
/*PyErr_Format(error_obj, "'%%c' expected after '%%c'",
dialect->delimiter,
dialect->quotechar);*/
return -1;
}
break;
case EAT_CRNL:
if (c == '\n' || c == '\r')
;
else if (c == '\0')
self->state = START_RECORD;
else {
/*PyErr_Format(error_obj, "new-line character seen in unquoted field - do you need to open the file in universal-newline mode?");*/
return -1;
}
break;
}
return 0;
}
static void
parse_reset(ReaderObj *self, char *rawline)
{
self->field = rawline;
self->state = START_RECORD;
self->numeric_field = 0;
}
long parse_line(char *rawline, long inputlength)
{
char *p;
ReaderObj reader;
parse_reset(&reader, rawline);
for (p=rawline; inputlength > 0; inputlength--, p++) {
if (parse_process_char(&reader, *p) < 0)
return -1;
}
if (parse_process_char(&reader, 0) < 0)
return -1;
return reader.field - rawline - 1;
}
''' % d)
ffi.compile()
def fastcsv_reader(f, dialect_name):
try:
module = __import__('_fastcsv_' + dialect_name)
except ImportError:
_make_ffi_from_dialect(dialect_name)
module = __import__('_fastcsv_' + dialect_name)
ffi, lib = module.ffi, module.lib
#
linelen = -1
for line in f:
if linelen <= len(line):
linelen = 2 * len(line)
rawline = ffi.new("char[]", linelen)
ffi.buffer(rawline, len(line))[:] = line
n = lib.parse_line(rawline, len(line))
assert n >= 0
yield ffi.buffer(rawline, n)[:].split('\x00')
if __name__ == '__main__':
csv.register_dialect('unixpwd', delimiter=':', quoting=csv.QUOTE_NONE)
with open('/etc/passwd', 'rb') as f:
reader = fastcsv_reader(f, 'unixpwd')
for row in reader:
print row