smarter_csv 1.14.3 → 1.14.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 79d1b26219dfb35f385c30eea361bc90d4b1b6cfc1030b6abb9339cc99eb39de
4
- data.tar.gz: ab0915f4193b657c7e8380ea3bc924a4efb40f9171fb3f120aef462d6f464370
3
+ metadata.gz: a84cfa57008a6f9f05ee82eeef9edb4dab5993874a38c456ce78d8da999280aa
4
+ data.tar.gz: 7e8569670615a6ff7fb63c152d8e849069bcde318edf885d95cef52b5513f52c
5
5
  SHA512:
6
- metadata.gz: 722a598f1581423d4295eb3ce6f46bf556fbf0206dbd4229eceff68625f60ad83d7e3f2dda3a5b6d61c18be2ff6d3d0fb495c46427bb2cc20fcf5f72fa58e317
7
- data.tar.gz: f61d37aa5a64e37601a7b12ce57a528bc26fec8ee643e4171040f0e9021a2fe4fdeec71698638e98e29da27248e51e77792ed5053bb252716298219f4b095e85
6
+ metadata.gz: 3d1baa73a0120824390f062e38d3acab8dfc7a09d48fbbdd647466605ccd543ca839e61cbefca9929d2dafce9a61b12e50b62d9e034aadd047a46e1886728980
7
+ data.tar.gz: 9756f2fdd15e619ba98011370a3410781b7771edd0656dd1bcb1226ebf899726ec00f43627dcc16756d986a962e9eb3e84c7e901ef5aecf12ede9f077f7a8423
data/CHANGELOG.md CHANGED
@@ -1,6 +1,9 @@
1
1
 
2
2
  # SmarterCSV 1.x Change Log
3
3
 
4
+ ## 1.14.4 (2025-05-26)
5
+ * Bugfix: SmarterCSV::Reader fixing issue with header containing spaces ([PR 305](https://github.com/tilo/smarter_csv/pull/305) thanks to Felipe Cabezudo)
6
+
4
7
  ## 1.14.3 (2025-05-04)
5
8
  * Improved C-extension parsing logic:
6
9
  - Added fast path for unquoted fields to avoid unnecessary quote checks.
data/CONTRIBUTORS.md CHANGED
@@ -59,3 +59,4 @@ A Big Thank you to everyone who filed issues, sent comments, and who contributed
59
59
  * [Randall B](https://github.com/randall-coding)
60
60
  * [Matthew Kennedy](https://github.com/MattKitmanLabs)
61
61
  * [Robert Reiz](https://github.com/reiz)
62
+ * [Felipe Cabezudo](https://github.com/felipekb)
@@ -32,7 +32,7 @@ module SmarterCSV
32
32
 
33
33
  if candidates.values.max == 0
34
34
  # if the header only contains
35
- return ',' if line.chomp(options[:row_sep]) =~ /^\w+$/
35
+ return ',' if line.chomp(options[:row_sep]) =~ /^[\w\s]+$/
36
36
 
37
37
  raise SmarterCSV::NoColSepDetected
38
38
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SmarterCSV
4
- VERSION = "1.14.3"
4
+ VERSION = "1.14.4"
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: smarter_csv
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.14.3
4
+ version: 1.14.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tilo Sloboda
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-05-05 00:00:00.000000000 Z
11
+ date: 2025-05-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: awesome_print
@@ -127,20 +127,8 @@ files:
127
127
  - docs/options.md
128
128
  - docs/row_col_sep.md
129
129
  - docs/value_converters.md
130
- - ext/smarter_csv/Makefile
131
130
  - ext/smarter_csv/extconf.rb
132
131
  - ext/smarter_csv/smarter_csv.c
133
- - ext/smarter_csv/smarter_csv.c.works
134
- - ext/smarter_csv/smarter_csv.c.works10
135
- - ext/smarter_csv/smarter_csv.c.works11
136
- - ext/smarter_csv/smarter_csv.c.works14
137
- - ext/smarter_csv/smarter_csv.c.works15
138
- - ext/smarter_csv/smarter_csv.c.works2
139
- - ext/smarter_csv/smarter_csv.c.works4
140
- - ext/smarter_csv/smarter_csv.c.works5
141
- - ext/smarter_csv/smarter_csv.c.works7
142
- - ext/smarter_csv/smarter_csv.c.works8
143
- - ext/smarter_csv/smarter_csv.c.works9
144
132
  - lib/smarter_csv.rb
145
133
  - lib/smarter_csv/auto_detection.rb
146
134
  - lib/smarter_csv/errors.rb
@@ -1,270 +0,0 @@
1
-
2
- SHELL = /bin/sh
3
-
4
- # V=0 quiet, V=1 verbose. other values don't work.
5
- V = 0
6
- V0 = $(V:0=)
7
- Q1 = $(V:1=)
8
- Q = $(Q1:0=@)
9
- ECHO1 = $(V:1=@ :)
10
- ECHO = $(ECHO1:0=@ echo)
11
- NULLCMD = :
12
-
13
- #### Start of system configuration section. ####
14
-
15
- srcdir = .
16
- topdir = /Users/tilo/.rvm/rubies/ruby-3.2.2/include/ruby-3.2.0
17
- hdrdir = $(topdir)
18
- arch_hdrdir = /Users/tilo/.rvm/rubies/ruby-3.2.2/include/ruby-3.2.0/arm64-darwin23
19
- PATH_SEPARATOR = :
20
- VPATH = $(srcdir):$(arch_hdrdir)/ruby:$(hdrdir)/ruby
21
- prefix = $(DESTDIR)/Users/tilo/.rvm/rubies/ruby-3.2.2
22
- rubysitearchprefix = $(rubylibprefix)/$(sitearch)
23
- rubyarchprefix = $(rubylibprefix)/$(arch)
24
- rubylibprefix = $(libdir)/$(RUBY_BASE_NAME)
25
- exec_prefix = $(prefix)
26
- vendorarchhdrdir = $(vendorhdrdir)/$(sitearch)
27
- sitearchhdrdir = $(sitehdrdir)/$(sitearch)
28
- rubyarchhdrdir = $(rubyhdrdir)/$(arch)
29
- vendorhdrdir = $(rubyhdrdir)/vendor_ruby
30
- sitehdrdir = $(rubyhdrdir)/site_ruby
31
- rubyhdrdir = $(includedir)/$(RUBY_VERSION_NAME)
32
- vendorarchdir = $(vendorlibdir)/$(sitearch)
33
- vendorlibdir = $(vendordir)/$(ruby_version)
34
- vendordir = $(rubylibprefix)/vendor_ruby
35
- sitearchdir = $(sitelibdir)/$(sitearch)
36
- sitelibdir = $(sitedir)/$(ruby_version)
37
- sitedir = $(rubylibprefix)/site_ruby
38
- rubyarchdir = $(rubylibdir)/$(arch)
39
- rubylibdir = $(rubylibprefix)/$(ruby_version)
40
- sitearchincludedir = $(includedir)/$(sitearch)
41
- archincludedir = $(includedir)/$(arch)
42
- sitearchlibdir = $(libdir)/$(sitearch)
43
- archlibdir = $(libdir)/$(arch)
44
- ridir = $(datarootdir)/$(RI_BASE_NAME)
45
- mandir = $(datarootdir)/man
46
- localedir = $(datarootdir)/locale
47
- libdir = $(exec_prefix)/lib
48
- psdir = $(docdir)
49
- pdfdir = $(docdir)
50
- dvidir = $(docdir)
51
- htmldir = $(docdir)
52
- infodir = $(datarootdir)/info
53
- docdir = $(datarootdir)/doc/$(PACKAGE)
54
- oldincludedir = $(DESTDIR)/usr/include
55
- includedir = $(SDKROOT)$(prefix)/include
56
- runstatedir = $(localstatedir)/run
57
- localstatedir = $(prefix)/var
58
- sharedstatedir = $(prefix)/com
59
- sysconfdir = $(prefix)/etc
60
- datadir = $(datarootdir)
61
- datarootdir = $(prefix)/share
62
- libexecdir = $(exec_prefix)/libexec
63
- sbindir = $(exec_prefix)/sbin
64
- bindir = $(exec_prefix)/bin
65
- archdir = $(rubyarchdir)
66
-
67
-
68
- CC_WRAPPER =
69
- CC = gcc
70
- CXX = g++
71
- LIBRUBY = $(LIBRUBY_SO)
72
- LIBRUBY_A = lib$(RUBY_SO_NAME)-static.a
73
- LIBRUBYARG_SHARED = -l$(RUBY_SO_NAME)
74
- LIBRUBYARG_STATIC = -l$(RUBY_SO_NAME)-static -framework CoreFoundation $(MAINLIBS)
75
- empty =
76
- OUTFLAG = -o $(empty)
77
- COUTFLAG = -o $(empty)
78
- CSRCFLAG = $(empty)
79
-
80
- RUBY_EXTCONF_H =
81
- cflags = -fdeclspec $(optflags) $(debugflags) $(warnflags)
82
- cxxflags =
83
- optflags = -O3
84
- debugflags = -ggdb3
85
- warnflags = -Wall -Wextra -Wextra-tokens -Wdeprecated-declarations -Wdivision-by-zero -Wdiv-by-zero -Wimplicit-function-declaration -Wimplicit-int -Wmisleading-indentation -Wpointer-arith -Wshorten-64-to-32 -Wwrite-strings -Wold-style-definition -Wmissing-noreturn -Wno-cast-function-type -Wno-constant-logical-operand -Wno-long-long -Wno-missing-field-initializers -Wno-overlength-strings -Wno-parentheses-equality -Wno-self-assign -Wno-tautological-compare -Wno-unused-parameter -Wno-unused-value -Wunused-variable -Wundef
86
- cppflags =
87
- CCDLFLAGS = -fno-common
88
- CFLAGS = $(CCDLFLAGS) -O3 -I/opt/homebrew/opt/libyaml/include -I/opt/homebrew/opt/libksba/include -I/opt/homebrew/opt/readline/include -I/opt/homebrew/opt/zlib/include -I/opt/homebrew/opt/openssl@1.1/include $(cflags) -fno-common -pipe $(ARCH_FLAG)
89
- INCFLAGS = -I. -I$(arch_hdrdir) -I$(hdrdir)/ruby/backward -I$(hdrdir) -I$(srcdir)
90
- DEFS =
91
- CPPFLAGS = -D_XOPEN_SOURCE -D_DARWIN_C_SOURCE -D_DARWIN_UNLIMITED_SELECT -D_REENTRANT $(DEFS) $(cppflags)
92
- CXXFLAGS = $(CCDLFLAGS) -fdeclspec $(ARCH_FLAG)
93
- ldflags = -L. -L/opt/homebrew/opt/libyaml/lib -L/opt/homebrew/opt/libksba/lib -L/opt/homebrew/opt/readline/lib -L/opt/homebrew/opt/zlib/lib -L/opt/homebrew/opt/openssl@1.1/lib -fstack-protector-strong
94
- dldflags = -L/opt/homebrew/opt/libyaml/lib -L/opt/homebrew/opt/libksba/lib -L/opt/homebrew/opt/readline/lib -L/opt/homebrew/opt/zlib/lib -L/opt/homebrew/opt/openssl@1.1/lib -Wl,-undefined,dynamic_lookup $(LIBRUBYARG_SHARED)
95
- ARCH_FLAG =
96
- DLDFLAGS = $(ldflags) $(dldflags) $(ARCH_FLAG)
97
- LDSHARED = $(CC) -dynamic -bundle
98
- LDSHAREDXX = $(CXX) -dynamic -bundle
99
- AR = ar
100
- EXEEXT =
101
-
102
- RUBY_INSTALL_NAME = $(RUBY_BASE_NAME)
103
- RUBY_SO_NAME = ruby.3.2
104
- RUBYW_INSTALL_NAME =
105
- RUBY_VERSION_NAME = $(RUBY_BASE_NAME)-$(ruby_version)
106
- RUBYW_BASE_NAME = rubyw
107
- RUBY_BASE_NAME = ruby
108
-
109
- arch = arm64-darwin23
110
- sitearch = $(arch)
111
- ruby_version = 3.2.0
112
- ruby = $(bindir)/$(RUBY_BASE_NAME)
113
- RUBY = $(ruby)
114
- BUILTRUBY = $(bindir)/$(RUBY_BASE_NAME)
115
- ruby_headers = $(hdrdir)/ruby.h $(hdrdir)/ruby/backward.h $(hdrdir)/ruby/ruby.h $(hdrdir)/ruby/defines.h $(hdrdir)/ruby/missing.h $(hdrdir)/ruby/intern.h $(hdrdir)/ruby/st.h $(hdrdir)/ruby/subst.h $(arch_hdrdir)/ruby/config.h
116
-
117
- RM = rm -f
118
- RM_RF = rm -fr
119
- RMDIRS = rmdir -p
120
- MAKEDIRS = /opt/homebrew/opt/coreutils/bin/gmkdir -p
121
- INSTALL = /opt/homebrew/opt/coreutils/bin/ginstall -c
122
- INSTALL_PROG = $(INSTALL) -m 0755
123
- INSTALL_DATA = $(INSTALL) -m 644
124
- COPY = cp
125
- TOUCH = exit >
126
-
127
- #### End of system configuration section. ####
128
-
129
- preload =
130
- libpath = . $(libdir)
131
- LIBPATH = -L. -L$(libdir)
132
- DEFFILE =
133
-
134
- CLEANFILES = mkmf.log
135
- DISTCLEANFILES =
136
- DISTCLEANDIRS =
137
-
138
- extout =
139
- extout_prefix =
140
- target_prefix = /smarter_csv
141
- LOCAL_LIBS =
142
- LIBS = $(LIBRUBYARG_SHARED) -lpthread
143
- ORIG_SRCS = smarter_csv.c
144
- SRCS = $(ORIG_SRCS)
145
- OBJS = smarter_csv.o
146
- HDRS =
147
- LOCAL_HDRS =
148
- TARGET = smarter_csv
149
- TARGET_NAME = smarter_csv
150
- TARGET_ENTRY = Init_$(TARGET_NAME)
151
- DLLIB = $(TARGET).bundle
152
- EXTSTATIC =
153
- STATIC_LIB =
154
-
155
- TIMESTAMP_DIR = .
156
- BINDIR = $(bindir)
157
- RUBYCOMMONDIR = $(sitedir)$(target_prefix)
158
- RUBYLIBDIR = $(sitelibdir)$(target_prefix)
159
- RUBYARCHDIR = $(sitearchdir)$(target_prefix)
160
- HDRDIR = $(sitehdrdir)$(target_prefix)
161
- ARCHHDRDIR = $(sitearchhdrdir)$(target_prefix)
162
- TARGET_SO_DIR =
163
- TARGET_SO = $(TARGET_SO_DIR)$(DLLIB)
164
- CLEANLIBS = $(TARGET_SO) $(TARGET_SO).dSYM
165
- CLEANOBJS = $(OBJS) *.bak
166
- TARGET_SO_DIR_TIMESTAMP = $(TIMESTAMP_DIR)/.sitearchdir.-.smarter_csv.time
167
-
168
- all: $(DLLIB)
169
- static: $(STATIC_LIB)
170
- .PHONY: all install static install-so install-rb
171
- .PHONY: clean clean-so clean-static clean-rb
172
-
173
- clean-static::
174
- clean-rb-default::
175
- clean-rb::
176
- clean-so::
177
- clean: clean-so clean-static clean-rb-default clean-rb
178
- -$(Q)$(RM_RF) $(CLEANLIBS) $(CLEANOBJS) $(CLEANFILES) .*.time
179
-
180
- distclean-rb-default::
181
- distclean-rb::
182
- distclean-so::
183
- distclean-static::
184
- distclean: clean distclean-so distclean-static distclean-rb-default distclean-rb
185
- -$(Q)$(RM) Makefile $(RUBY_EXTCONF_H) conftest.* mkmf.log
186
- -$(Q)$(RM) core ruby$(EXEEXT) *~ $(DISTCLEANFILES)
187
- -$(Q)$(RMDIRS) $(DISTCLEANDIRS) 2> /dev/null || true
188
-
189
- realclean: distclean
190
- install: install-so install-rb
191
-
192
- install-so: $(DLLIB) $(TARGET_SO_DIR_TIMESTAMP)
193
- $(INSTALL_PROG) $(DLLIB) $(RUBYARCHDIR)
194
- clean-static::
195
- -$(Q)$(RM) $(STATIC_LIB)
196
- install-rb: pre-install-rb do-install-rb install-rb-default
197
- install-rb-default: pre-install-rb-default do-install-rb-default
198
- pre-install-rb: Makefile
199
- pre-install-rb-default: Makefile
200
- do-install-rb:
201
- do-install-rb-default:
202
- pre-install-rb-default:
203
- @$(NULLCMD)
204
- $(TARGET_SO_DIR_TIMESTAMP):
205
- $(Q) $(MAKEDIRS) $(@D) $(RUBYARCHDIR)
206
- $(Q) $(TOUCH) $@
207
-
208
- site-install: site-install-so site-install-rb
209
- site-install-so: install-so
210
- site-install-rb: install-rb
211
-
212
- .SUFFIXES: .c .m .cc .mm .cxx .cpp .o .S
213
-
214
- .cc.o:
215
- $(ECHO) compiling $(<)
216
- $(Q) $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) $(COUTFLAG)$@ -c $(CSRCFLAG)$<
217
-
218
- .cc.S:
219
- $(ECHO) translating $(<)
220
- $(Q) $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) $(COUTFLAG)$@ -S $(CSRCFLAG)$<
221
-
222
- .mm.o:
223
- $(ECHO) compiling $(<)
224
- $(Q) $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) $(COUTFLAG)$@ -c $(CSRCFLAG)$<
225
-
226
- .mm.S:
227
- $(ECHO) translating $(<)
228
- $(Q) $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) $(COUTFLAG)$@ -S $(CSRCFLAG)$<
229
-
230
- .cxx.o:
231
- $(ECHO) compiling $(<)
232
- $(Q) $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) $(COUTFLAG)$@ -c $(CSRCFLAG)$<
233
-
234
- .cxx.S:
235
- $(ECHO) translating $(<)
236
- $(Q) $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) $(COUTFLAG)$@ -S $(CSRCFLAG)$<
237
-
238
- .cpp.o:
239
- $(ECHO) compiling $(<)
240
- $(Q) $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) $(COUTFLAG)$@ -c $(CSRCFLAG)$<
241
-
242
- .cpp.S:
243
- $(ECHO) translating $(<)
244
- $(Q) $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) $(COUTFLAG)$@ -S $(CSRCFLAG)$<
245
-
246
- .c.o:
247
- $(ECHO) compiling $(<)
248
- $(Q) $(CC) $(INCFLAGS) $(CPPFLAGS) $(CFLAGS) $(COUTFLAG)$@ -c $(CSRCFLAG)$<
249
-
250
- .c.S:
251
- $(ECHO) translating $(<)
252
- $(Q) $(CC) $(INCFLAGS) $(CPPFLAGS) $(CFLAGS) $(COUTFLAG)$@ -S $(CSRCFLAG)$<
253
-
254
- .m.o:
255
- $(ECHO) compiling $(<)
256
- $(Q) $(CC) $(INCFLAGS) $(CPPFLAGS) $(CFLAGS) $(COUTFLAG)$@ -c $(CSRCFLAG)$<
257
-
258
- .m.S:
259
- $(ECHO) translating $(<)
260
- $(Q) $(CC) $(INCFLAGS) $(CPPFLAGS) $(CFLAGS) $(COUTFLAG)$@ -S $(CSRCFLAG)$<
261
-
262
- $(TARGET_SO): $(OBJS) Makefile
263
- $(ECHO) linking shared-object smarter_csv/$(DLLIB)
264
- -$(Q)$(RM) $(@)
265
- $(Q) $(LDSHARED) -o $@ $(OBJS) $(LIBPATH) $(DLDFLAGS) $(LOCAL_LIBS) $(LIBS)
266
- $(Q) $(POSTLINK)
267
-
268
-
269
-
270
- $(OBJS): $(HDRS) $(ruby_headers)
@@ -1,185 +0,0 @@
1
- #include "ruby.h"
2
- #include "ruby/encoding.h"
3
- #include <stdio.h>
4
- #include <stdbool.h>
5
-
6
- #ifndef bool
7
- #define bool int
8
- #define false ((bool)0)
9
- #define true ((bool)1)
10
- #endif
11
-
12
- VALUE SmarterCSV = Qnil;
13
- VALUE eMalformedCSVError = Qnil;
14
- VALUE Parser = Qnil;
15
-
16
- static VALUE Qempty_string; // shared frozen empty string
17
-
18
- static VALUE rb_parse_csv_line(VALUE self, VALUE line, VALUE col_sep, VALUE quote_char, VALUE max_size) {
19
- if (RB_TYPE_P(line, T_NIL) == 1) {
20
- return rb_ary_new();
21
- }
22
-
23
- if (RB_TYPE_P(line, T_STRING) != 1) {
24
- rb_raise(rb_eTypeError, "ERROR in SmarterCSV.parse_line: line has to be a string or nil");
25
- }
26
-
27
- rb_encoding *encoding = rb_enc_get(line); /* get the encoding from the input line */
28
- char *startP = RSTRING_PTR(line); /* may not be null terminated */
29
- long line_len = RSTRING_LEN(line);
30
- char *endP = startP + line_len; /* points behind the string */
31
- char *p = startP;
32
-
33
- char *col_sepP = RSTRING_PTR(col_sep);
34
- long col_sep_len = RSTRING_LEN(col_sep);
35
-
36
- char *quoteP = RSTRING_PTR(quote_char);
37
- char quote = *quoteP;
38
- long quote_count = 0;
39
-
40
- bool col_sep_found = true;
41
-
42
- VALUE elements = rb_ary_new();
43
- VALUE field;
44
- long i;
45
-
46
- /* Variables for escaped quote handling */
47
- long backslash_count = 0;
48
- bool in_quotes = false;
49
-
50
- /* Optimization 1: maintain count instead of calling RARRAY_LEN repeatedly */
51
- long element_count = 0;
52
-
53
- /* Optimization 2: cache max_size value if not nil */
54
- int max_fields = -1;
55
- if (max_size != Qnil) {
56
- max_fields = NUM2INT(max_size);
57
- if (max_fields < 0) {
58
- return rb_ary_new(); // Return empty array early
59
- }
60
- }
61
-
62
- while (p < endP) {
63
- /* does the remaining string start with col_sep ? */
64
- col_sep_found = true;
65
- for (i = 0; (i < col_sep_len) && (p + i < endP); i++) {
66
- col_sep_found = col_sep_found && (*(p + i) == *(col_sepP + i));
67
- }
68
-
69
- /* if col_sep was found and we're not inside quotes */
70
- if (col_sep_found && !in_quotes) {
71
- if ((max_fields >= 0) && (element_count >= max_fields)) {
72
- break;
73
- } else {
74
- bool only_spaces = true;
75
- for (char *s = startP; s < p; s++) {
76
- if (*s != ' ') {
77
- only_spaces = false;
78
- break;
79
- }
80
- }
81
-
82
- if (only_spaces) {
83
- field = Qempty_string;
84
- } else {
85
- long field_len = p - startP;
86
-
87
- // fast-path quote cleanup: if field starts and ends with quote, and no doubled quotes inside
88
- if (field_len >= 2 && startP[0] == quote && startP[field_len - 1] == quote) {
89
- char *inner_start = startP + 1;
90
- long inner_len = field_len - 2;
91
- bool has_double_quote = false;
92
- for (i = 0; i < inner_len - 1; i++) {
93
- if (inner_start[i] == quote && inner_start[i + 1] == quote) {
94
- has_double_quote = true;
95
- break;
96
- }
97
- }
98
- if (!has_double_quote) {
99
- field = rb_enc_str_new(inner_start, inner_len, encoding);
100
- } else {
101
- field = rb_enc_str_new(startP, field_len, encoding);
102
- }
103
- } else {
104
- field = rb_enc_str_new(startP, field_len, encoding);
105
- }
106
- }
107
-
108
- rb_ary_push(elements, field);
109
- element_count++;
110
-
111
- p += col_sep_len;
112
- startP = p;
113
- backslash_count = 0; // Reset backslash count at the start of a new field
114
- }
115
- } else {
116
- if (*p == '\\') {
117
- backslash_count++;
118
- } else {
119
- if (*p == quote) {
120
- if (backslash_count % 2 == 0) {
121
- in_quotes = !in_quotes;
122
- }
123
- }
124
- backslash_count = 0;
125
- }
126
- p++;
127
- }
128
- }
129
-
130
- if (in_quotes) {
131
- rb_raise(eMalformedCSVError, "Unclosed quoted field detected in line: %s", StringValueCStr(line));
132
- }
133
-
134
- if ((max_fields < 0) || (element_count < max_fields)) {
135
- bool only_spaces = true;
136
- for (char *s = startP; s < endP; s++) {
137
- if (*s != ' ') {
138
- only_spaces = false;
139
- break;
140
- }
141
- }
142
-
143
- if (only_spaces) {
144
- field = Qempty_string;
145
- } else {
146
- long field_len = endP - startP;
147
-
148
- // fast-path quote cleanup on final field
149
- if (field_len >= 2 && startP[0] == quote && startP[field_len - 1] == quote) {
150
- char *inner_start = startP + 1;
151
- long inner_len = field_len - 2;
152
- bool has_double_quote = false;
153
- for (i = 0; i < inner_len - 1; i++) {
154
- if (inner_start[i] == quote && inner_start[i + 1] == quote) {
155
- has_double_quote = true;
156
- break;
157
- }
158
- }
159
- if (!has_double_quote) {
160
- field = rb_enc_str_new(inner_start, inner_len, encoding);
161
- } else {
162
- field = rb_enc_str_new(startP, field_len, encoding);
163
- }
164
- } else {
165
- field = rb_enc_str_new(startP, field_len, encoding);
166
- }
167
- }
168
-
169
- rb_ary_push(elements, field);
170
- }
171
-
172
- return elements;
173
- }
174
-
175
- void Init_smarter_csv(void) {
176
- SmarterCSV = rb_const_get(rb_cObject, rb_intern("SmarterCSV"));
177
- Parser = rb_const_get(SmarterCSV, rb_intern("Parser"));
178
- eMalformedCSVError = rb_const_get(SmarterCSV, rb_intern("MalformedCSV"));
179
-
180
- Qempty_string = rb_str_new_literal("");
181
- rb_obj_freeze(Qempty_string);
182
- rb_global_variable(&Qempty_string);
183
-
184
- rb_define_module_function(Parser, "parse_csv_line_c", rb_parse_csv_line, 4);
185
- }