@@ -247,6 +247,8 @@ def sniff(self, sample, delimiters=None):
247247 that order, no matter how many times each of them occurs.
248248 """
249249
250+ sample = sample .replace ('\r \n ' , '\n ' ).replace ('\r ' , '\n ' )
251+
250252 quotechar , doublequote , delimiter , skipinitialspace = \
251253 self ._guess_quote_and_delimiter (sample , delimiters )
252254 if not delimiter :
@@ -284,12 +286,16 @@ def _guess_quote_and_delimiter(self, data, delimiters):
284286 """
285287 import re
286288
289+ # The body of a quoted field ends at the first quote which is
290+ # not doubled, as it does for a reader. A lazy ".*?" scans to
291+ # the end of the sample instead, from every start: quadratically.
292+ body = r'(?:(?P=quote){2}|(?!(?P=quote)).)*+'
287293 matches = []
288- for restr in (r'(?P<delim>[^\w\n"\'])(?P<space> ?)(?P<quote>["\']).*? (?P=quote)(?P=delim)' , # ,".*? ",
289- r'(?:^|\n)(?P<quote>["\']).*? (?P=quote)(?P<delim>[^\w\n"\'])(?P<space> ?)' , # ".*? ",
290- r'(?P<delim>[^\w\n"\'])(?P<space> ?)(?P<quote>["\']).*? (?P=quote)(?:$|\r|\ n)' , # ,".*? "
291- r'(?:^|\n)(?P<quote>["\']).*? (?P=quote)(?:$|\r|\ n)' ): # ".*? " (no delim, no space)
292- regexp = re .compile (restr , re .DOTALL | re .MULTILINE )
294+ for restr in (r'(?P<delim>[^\w\n"\'])(?P<space> ?)(?P<quote>["\'])%s (?P=quote)(?P=delim)' , # ,"... ",
295+ r'(?:^|\n)(?P<quote>["\'])%s (?P=quote)(?P<delim>[^\w\n"\'])(?P<space> ?)' , # "... ",
296+ r'(?P<delim>[^\w\n"\'])(?P<space> ?)(?P<quote>["\'])%s (?P=quote)(?:$|\n)' , # ,"... "
297+ r'(?:^|\n)(?P<quote>["\'])%s (?P=quote)(?:$|\n)' ): # "... " (no delim, no space)
298+ regexp = re .compile (restr % body , re .DOTALL | re .MULTILINE )
293299 matches = regexp .findall (data )
294300 if matches :
295301 break
@@ -332,18 +338,22 @@ def _guess_quote_and_delimiter(self, data, delimiters):
332338 delim = ''
333339 skipinitialspace = 0
334340
335- # if we see an extra quote between delimiters, we've got a
336- # double quoted format
337- dq_regexp = re .compile (
338- r"((%(delim)s)|^)\W*%(quote)s[^%(delim)s\n]*%(quote)s[^%(delim)s\n]*%(quote)s\W*((%(delim)s)|$)" % \
339- {'delim' :re .escape (delim ), 'quote' :quotechar }, re .MULTILINE )
340-
341-
342-
343- if dq_regexp .search (data ):
344- doublequote = True
345- else :
346- doublequote = False
341+ # A doubled quote character inside a quoted field means
342+ # a double quoted format. Match whole fields, so that a match
343+ # cannot slide across field boundaries.
344+ doublequote = False
345+ if delim :
346+ dq_regexp = re .compile (
347+ r"(?:(?<=%(delim)s)|^)%(space)s%(quote)s" # ,"
348+ r"((?:%(quote)s%(quote)s|[^%(quote)s]++)*+)" # the body
349+ r"%(quote)s(?:%(delim)s|$)" # ",
350+ % {'delim' : re .escape (delim ), 'quote' : quotechar ,
351+ # Skipping spaces after a space rescans them.
352+ 'space' : ' *+' if delim != ' ' else '' },
353+ re .MULTILINE )
354+ dquotechar = quotechar * 2
355+ doublequote = any (dquotechar in m [1 ]
356+ for m in dq_regexp .finditer (data ))
347357
348358 return (quotechar , doublequote , delim , skipinitialspace )
349359
0 commit comments