dsv 0.12.5 → 0.12.7
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 +4 -4
- data/CHANGELOG +14 -0
- data/lib/DSV/VERSION.rb +1 -1
- data/lib/dsv.rb +11 -4
- data/test/dsv_test.rb +26 -0
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ac547ecbc72cfc0cfb3b4e2a1f9c0c5327ea1f4e064c126a28aa85f8c3a42ded
|
|
4
|
+
data.tar.gz: 5be7f5303ed207575c43dfacac8d7b4e68f259172b48d25aba60104af57b68f7
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 022fbe8f4caf5baf90c785ba1ef586574d270331e25b6734e1e982766e1a8836a3182f1f92452734ce1d3316e834e0b07da4b8f9b89f6105c45222e4505dab7b
|
|
7
|
+
data.tar.gz: 19cd8f16d5b72349be0e066f63a5dcbac7cc9ea1ab4736e67cd07507415794ce2f9cc47d9bc747358125c252480018b1a0170efd820bdab8056611f46971cfd0
|
data/CHANGELOG
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# dsv/CHANGELOG
|
|
2
2
|
|
|
3
|
+
## 20261027
|
|
4
|
+
|
|
5
|
+
0.12.7: ~ reassemble_quoted_fields(): an escaped quote before a separator no longer closes a quoted field.
|
|
6
|
+
|
|
7
|
+
1. ~ reassemble_quoted_fields(): a piece closes a field where it ends in an odd run of quotes; + trailing_quotes().
|
|
8
|
+
2. ~ test/dsv_test.rb: - the skip on a doubled quote before a separator.
|
|
9
|
+
3. ~ lib/DSV/VERSION.rb: /0.12.6/0.12.7/.
|
|
10
|
+
|
|
11
|
+
0.12.6: + quoting examples.
|
|
12
|
+
|
|
13
|
+
1. ~ test/dsv_test.rb: + a doubled quote before a separator, skipped; + a quote not opening a field read as data; + a stray quote not taking in the next line, skipped; + an unclosed quoted field raising UnterminatedQuote, skipped.
|
|
14
|
+
2. ~ lib/DSV/VERSION.rb: /0.12.5/0.12.6/.
|
|
15
|
+
|
|
16
|
+
|
|
3
17
|
## 20261016
|
|
4
18
|
|
|
5
19
|
0.12.5: Regexp separators and quote: :double made to do what the README says.
|
data/lib/DSV/VERSION.rb
CHANGED
data/lib/dsv.rb
CHANGED
|
@@ -332,19 +332,19 @@ class DSV
|
|
|
332
332
|
)
|
|
333
333
|
end
|
|
334
334
|
|
|
335
|
-
# Pieces of a row split on the separator, the pieces of a quoted field that held the separator joined back with it, or with the text it matched where the separator is a Regexp; a piece that is a lone quote opens or closes
|
|
335
|
+
# Pieces of a row split on the separator, the pieces of a quoted field that held the separator joined back with it, or with the text it matched where the separator is a Regexp; a piece closes a field where it ends in an odd run of quotes, a doubled quote being escaped, and a piece that is a lone quote opens or closes one.
|
|
336
336
|
def reassemble_quoted_fields(pieces, separators = nil)
|
|
337
337
|
fields = []
|
|
338
338
|
buffer = nil
|
|
339
339
|
pieces.each_with_index do |piece, i|
|
|
340
|
-
if buffer &&
|
|
340
|
+
if buffer && trailing_quotes(piece, -1).even?
|
|
341
341
|
buffer << separator_before(i, separators) << piece
|
|
342
342
|
elsif buffer
|
|
343
343
|
fields << (buffer << separator_before(i, separators) << piece.delete_suffix('"')).gsub('""', '"')
|
|
344
344
|
buffer = nil
|
|
345
|
-
elsif piece.start_with?('"') && (piece
|
|
345
|
+
elsif piece.start_with?('"') && trailing_quotes(piece, 0).even?
|
|
346
346
|
buffer = +piece.delete_prefix('"')
|
|
347
|
-
elsif piece.start_with?('"')
|
|
347
|
+
elsif piece.start_with?('"')
|
|
348
348
|
fields << piece.delete_prefix('"').delete_suffix('"').gsub('""', '"')
|
|
349
349
|
else
|
|
350
350
|
fields << piece
|
|
@@ -353,6 +353,13 @@ class DSV
|
|
|
353
353
|
fields
|
|
354
354
|
end
|
|
355
355
|
|
|
356
|
+
# How many quotes a piece ends with, counting back to but not including the byte given, so that a field's opening quote is not taken for part of its closing. Read by byte, a quote being 34 in any ASCII-compatible encoding, so that nothing is allocated per character.
|
|
357
|
+
def trailing_quotes(piece, floor)
|
|
358
|
+
i = piece.bytesize - 1
|
|
359
|
+
i -= 1 while i > floor && piece.getbyte(i) == 34
|
|
360
|
+
piece.bytesize - 1 - i
|
|
361
|
+
end
|
|
362
|
+
|
|
356
363
|
# What stood between a piece and the one before it: the separator, or the text a Regexp separator matched there.
|
|
357
364
|
def separator_before(i, separators)
|
|
358
365
|
separators ? separators[i - 1] : @column_separator
|
data/test/dsv_test.rb
CHANGED
|
@@ -242,6 +242,12 @@ describe DSV do
|
|
|
242
242
|
_(DSV.parse_line('foo,"""""",baz')).must_equal ['foo', '""', 'baz']
|
|
243
243
|
end
|
|
244
244
|
|
|
245
|
+
it "reads a doubled quote just before a separator inside a quoted field" do
|
|
246
|
+
_(DSV.parse_line('"He said ""no"", then left",x')).must_equal ['He said "no", then left', 'x']
|
|
247
|
+
_(DSV.parse_line('"a"",""b",c')).must_equal ['a","b', 'c']
|
|
248
|
+
_(DSV.read("\"a\"\",\"\"b\",\"c\"\n", quote: :double)).must_equal [{0 => 'a","b', 1 => 'c'}]
|
|
249
|
+
end
|
|
250
|
+
|
|
245
251
|
it "reads a trailing empty field as an empty string" do
|
|
246
252
|
_(DSV.parse_line('foo,bar,')).must_equal ['foo', 'bar', '']
|
|
247
253
|
_(DSV.parse_line(',')).must_equal ['', '']
|
|
@@ -548,6 +554,26 @@ describe DSV do
|
|
|
548
554
|
end
|
|
549
555
|
end
|
|
550
556
|
|
|
557
|
+
# Input which is not RFC 4180, read to one rule provisionally, until a policy for malformed quoting is decided: a quote is structure only where it opens a field, and a field it opens which does not close raises UnterminatedQuote.
|
|
558
|
+
describe "quoting on read, where the input is malformed" do
|
|
559
|
+
it "reads a quote which does not open a field as data" do
|
|
560
|
+
_(DSV.parse_line('a"b"c,d')).must_equal ['a"b"c', 'd']
|
|
561
|
+
_(DSV.parse_line('a, "b,c"')).must_equal ['a', ' "b', 'c"']
|
|
562
|
+
end
|
|
563
|
+
|
|
564
|
+
it "reads a stray quote in an unquoted field as data, without taking in the next line" do
|
|
565
|
+
skip "Observation: the quotes in a row are counted, so a stray quote leaves them unbalanced and the next line is taken into the row; unspecified since 0.11.4, :double since 0.12.5"
|
|
566
|
+
_(DSV.read("12\" pizza,9.99\nnext,row\n", as_array: true)).must_equal [['12" pizza', '9.99'], ['next', 'row']]
|
|
567
|
+
_(DSV.read("12\" pizza,9.99\nnext,row\n", as_array: true, quote: :double)).must_equal [['12" pizza', '9.99'], ['next', 'row']]
|
|
568
|
+
end
|
|
569
|
+
|
|
570
|
+
it "raises UnterminatedQuote on a quoted field which does not close" do
|
|
571
|
+
skip "Observation: a quoted field which does not close is dropped silently, with the rest of its row"
|
|
572
|
+
_{DSV.read("\"abc,d\n")}.must_raise DSV::UnterminatedQuote
|
|
573
|
+
_{DSV.read("\"ab\"c,d\n")}.must_raise DSV::UnterminatedQuote
|
|
574
|
+
end
|
|
575
|
+
end
|
|
576
|
+
|
|
551
577
|
describe "#write" do
|
|
552
578
|
it "writes each row in column order, double-quoted by default" do
|
|
553
579
|
output = written(columns: [:a, :b]){|csv| csv.rows = [{'a' => 1, 'b' => 2}]; csv.write}
|