From 141b721bb54a3c50344d0d580e95ea658017b66a Mon Sep 17 00:00:00 2001 From: Koichi-Kobayashi Date: Fri, 5 Jun 2015 14:42:47 +0900 Subject: [PATCH 1/2] =?UTF-8?q?=E5=88=97=E5=85=A8=E4=BD=93=E3=82=92?= =?UTF-8?q?=E5=9B=B2=E3=81=BF=E6=96=87=E5=AD=97=E3=81=A7=E5=9B=B2=E3=82=80?= =?UTF-8?q?=E3=82=A2=E3=83=8E=E3=83=86=E3=83=BC=E3=82=B7=E3=83=A7=E3=83=B3?= =?UTF-8?q?=E3=81=A8=E3=83=9D=E3=83=AA=E3=82=B7=E3=83=BC=E3=82=92=E8=BF=BD?= =?UTF-8?q?=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/orangesignal/csv/CsvWriter.java | 56 ++++++++++++++++++- .../com/orangesignal/csv/QuotePolicy.java | 9 ++- .../csv/annotation/CsvColumn.java | 9 +++ .../orangesignal/csv/io/CsvEntityWriter.java | 31 ++++++++-- .../com/orangesignal/csv/CsvWriterTest.java | 23 ++++++++ .../com/orangesignal/csv/QuotePolicyTest.java | 2 +- .../handlers/CsvEntityListHandlerTest.java | 30 ++++++++++ .../csv/io/CsvEntityWriterTest.java | 53 +++++++++++++++++- .../orangesignal/csv/model/SampleQuote.java | 38 +++++++++++++ 9 files changed, 242 insertions(+), 9 deletions(-) create mode 100644 src/test/java/com/orangesignal/csv/model/SampleQuote.java diff --git a/src/main/java/com/orangesignal/csv/CsvWriter.java b/src/main/java/com/orangesignal/csv/CsvWriter.java index a123275..44d3c46 100644 --- a/src/main/java/com/orangesignal/csv/CsvWriter.java +++ b/src/main/java/com/orangesignal/csv/CsvWriter.java @@ -139,6 +139,31 @@ private void ensureOpen() throws IOException { * @throws IOException 入出力エラーが発生した場合 */ public void writeValues(final List values) throws IOException { + writeValuesCore(values, null); + } + + /** + * 指定された CSV トークンの値リストを書き込みます。 + * + * @param values 書き込む CSV トークンの値リスト + * @param quotes 列全体を囲み文字で囲むかどうかのリスト + * @throws CsvValueException 可変項目数が禁止されている場合に項目数が一致しない場合 + * @throws IOException 入出力エラーが発生した場合 + * @since 2.2 + */ + public void writeValues(final List values, final List quotes) throws IOException { + writeValuesCore(values, quotes); + } + + /** + * 指定された CSV トークンの値リストを書き込みます。 + * + * @param values 書き込む CSV トークンの値リスト + * @param quotes 列全体を囲み文字で囲むかどうかのリスト + * @throws CsvValueException 可変項目数が禁止されている場合に項目数が一致しない場合 + * @throws IOException 入出力エラーが発生した場合 + */ + private void writeValuesCore(final List values, List quotes) throws IOException { synchronized (this) { ensureOpen(); @@ -150,11 +175,19 @@ public void writeValues(final List values) throws IOException { final StringBuilder buf = new StringBuilder(); if (values != null) { final int max = values.size(); + + if (quotes == null) { + quotes = new ArrayList(); + for (int i = 0; i < max; i++) { + quotes.add(false); + } + } + for (int i = 0; i < max; i++) { if (i > 0) { buf.append(cfg.getSeparator()); } - + String value = values.get(i); boolean enclose = false; // 項目を囲み文字で囲むかどうか if (value == null) { @@ -170,6 +203,18 @@ public void writeValues(final List values) throws IOException { enclose = true; break; + case COLUMN: + // CsvColumn の columnQuote が true の場合に列全体を囲み文字で囲みます。 + if (quotes.get(i)) { + enclose = true; + } else { + // 項目値に区切り文字、囲み文字、改行文字のいずれかを含む場合は囲み文字で囲むべきと判断します。 + enclose = value.indexOf(cfg.getSeparator()) != -1 + || value.indexOf(cfg.getQuote()) != -1 + || value.indexOf('\r') != -1 || value.indexOf('\n') != -1; + } + break; + case MINIMAL: default: // 項目値に区切り文字、囲み文字、改行文字のいずれかを含む場合は囲み文字で囲むべきと判断します。 @@ -262,6 +307,15 @@ private String escapeQuote(final String value) { ); } + /** + * 区切り文字形式情報を返します。 + * @return 区切り文字形式情報 + * @since 2.2 + */ + public CsvConfig getCfg() { + return cfg; + } + @Override public void flush() throws IOException { synchronized (this) { diff --git a/src/main/java/com/orangesignal/csv/QuotePolicy.java b/src/main/java/com/orangesignal/csv/QuotePolicy.java index a7a55ee..9101da0 100644 --- a/src/main/java/com/orangesignal/csv/QuotePolicy.java +++ b/src/main/java/com/orangesignal/csv/QuotePolicy.java @@ -32,7 +32,14 @@ public enum QuotePolicy { /** * 項目内に区切り文字、囲み文字または改行文字が含まれる場合にだけ項目を囲み文字で囲むようにします。 */ - MINIMAL; + MINIMAL, + + /** + * 列全体の項目を囲み文字で囲むようにします。

+ * {@link com.orangesignal.csv.annotation.CsvColumn#columnQuote} を true にして使用して下さい。 + * @since 2.2 + */ + COLUMN; // NON_NUMERIC diff --git a/src/main/java/com/orangesignal/csv/annotation/CsvColumn.java b/src/main/java/com/orangesignal/csv/annotation/CsvColumn.java index d4797f0..07cc7bc 100644 --- a/src/main/java/com/orangesignal/csv/annotation/CsvColumn.java +++ b/src/main/java/com/orangesignal/csv/annotation/CsvColumn.java @@ -133,4 +133,13 @@ */ String defaultValue() default ""; + /** + * 列全体に囲み文字を出力するかどうかを返します。

+ * {@link com.orangesignal.csv.QuotePolicy#COLUMN} と組み合わせて使います。 + * + * @return 列全体に囲み文字を出力するかどうか + * @since 2.2 + */ + boolean columnQuote() default false; + } \ No newline at end of file diff --git a/src/main/java/com/orangesignal/csv/io/CsvEntityWriter.java b/src/main/java/com/orangesignal/csv/io/CsvEntityWriter.java index 8ae4272..4be1aa8 100644 --- a/src/main/java/com/orangesignal/csv/io/CsvEntityWriter.java +++ b/src/main/java/com/orangesignal/csv/io/CsvEntityWriter.java @@ -271,17 +271,18 @@ public boolean write(final T entity) throws IOException { return true; } - final List values = toValues(entity); - if (template.isAccept(columnNames, values)) { + final Tuple2, List> values = toValues(entity); + if (template.isAccept(columnNames, values.value1)) { return false; } - writer.writeValues(values); + writer.writeValues(values.value1, values.value2); return true; } } - private List toValues(final T entity) throws IOException { + private Tuple2, List> toValues(final T entity) throws IOException { final String[] values = new String[columnCount]; + final Boolean[] quotes = new Boolean[columnCount]; for (final Field field : entity.getClass().getDeclaredFields()) { final CsvColumns columns = field.getAnnotation(CsvColumns.class); if (columns != null) { @@ -313,6 +314,12 @@ private List toValues(final T entity) throws IOException { if (values[pos] == null && column.required()) { throw new CsvColumnException(String.format("%s must not be null", columnNames.get(pos)), entity); } + if (values[pos] != null && values[pos] != "" && column.columnQuote()) { + // 列全体を囲み文字で囲むためにマークします。 + quotes[pos] = true; + } else { + quotes[pos] = false; + } } } final CsvColumn column = field.getAnnotation(CsvColumn.class); @@ -332,9 +339,15 @@ private List toValues(final T entity) throws IOException { if (values[pos] == null && column.required()) { throw new CsvColumnException(String.format("%s must not be null", columnNames.get(pos)), entity); } + if (values[pos] != null && values[pos] != "" && column.columnQuote()) { + // 列全体を囲み文字で囲むためにマークします。 + quotes[pos] = true; + } else { + quotes[pos] = false; + } } } - return Arrays.asList(values); + return new Tuple2, List>(Arrays.asList(values), Arrays.asList(quotes)); } // ------------------------------------------------------------------------ @@ -360,4 +373,12 @@ public boolean isDisableWriteHeader() { return disableWriteHeader; } + private class Tuple2 { + private T1 value1 = null; + private T2 value2 = null; + public Tuple2(T1 asList, T2 asList2) { + this.value1 = asList; + this.value2 = asList2; + } + } } \ No newline at end of file diff --git a/src/test/java/com/orangesignal/csv/CsvWriterTest.java b/src/test/java/com/orangesignal/csv/CsvWriterTest.java index 1a18efc..b5c71d6 100644 --- a/src/test/java/com/orangesignal/csv/CsvWriterTest.java +++ b/src/test/java/com/orangesignal/csv/CsvWriterTest.java @@ -205,6 +205,29 @@ public void testWriteUtf8bomToStringWriter() throws IOException { } */ + @Test + public void testWriteNoNQuote() throws IOException { + final CsvConfig cfg = new CsvConfig(',', '"', '\\'); + cfg.setEscapeDisabled(false); + cfg.setQuoteDisabled(false); + cfg.setQuotePolicy(QuotePolicy.COLUMN); + cfg.setNullString("NULL"); + cfg.setLineSeparator("\r\n"); + + final StringWriter sw = new StringWriter(); + final CsvWriter writer = new CsvWriter(sw, cfg); + try { + // Act + writer.writeValues(Arrays.asList(new String[]{ "aaa", "b,\"b", "ccc" })); + writer.writeValues(Arrays.asList(new String[]{ "zzz", "", null })); + writer.flush(); + // Assert + assertThat(sw.getBuffer().toString(), is("aaa,\"b,\\\"b\",ccc\r\nzzz,,NULL\r\n")); + } finally { + writer.close(); + } + } + @Test public void testWriteValuesCsvValueException() throws IOException { final CsvConfig cfg = new CsvConfig(); diff --git a/src/test/java/com/orangesignal/csv/QuotePolicyTest.java b/src/test/java/com/orangesignal/csv/QuotePolicyTest.java index bc53677..3ffe5d8 100644 --- a/src/test/java/com/orangesignal/csv/QuotePolicyTest.java +++ b/src/test/java/com/orangesignal/csv/QuotePolicyTest.java @@ -32,7 +32,7 @@ public void testValues() { final QuotePolicy[] values = QuotePolicy.values(); for (final QuotePolicy value : values) { switch (value) { - case ALL: case MINIMAL: + case ALL: case MINIMAL: case COLUMN: break; default: fail(); diff --git a/src/test/java/com/orangesignal/csv/handlers/CsvEntityListHandlerTest.java b/src/test/java/com/orangesignal/csv/handlers/CsvEntityListHandlerTest.java index b4d5bce..f2815b0 100644 --- a/src/test/java/com/orangesignal/csv/handlers/CsvEntityListHandlerTest.java +++ b/src/test/java/com/orangesignal/csv/handlers/CsvEntityListHandlerTest.java @@ -36,11 +36,13 @@ import com.orangesignal.csv.CsvConfig; import com.orangesignal.csv.CsvReader; import com.orangesignal.csv.CsvWriter; +import com.orangesignal.csv.QuotePolicy; import com.orangesignal.csv.entity.Price; import com.orangesignal.csv.entity.Price2; import com.orangesignal.csv.filters.SimpleBeanFilter; import com.orangesignal.csv.filters.SimpleCsvNamedValueFilter; import com.orangesignal.csv.model.SampleBean; +import com.orangesignal.csv.model.SampleQuote; /** * {@link CsvEntityListHandler} クラスの単体テストです。 @@ -217,4 +219,32 @@ public void testSaveFilter() throws Exception { assertThat(sw.getBuffer().toString(), is("シンボル,名称,価格,出来高,日付,時刻\r\nGCV09,COMEX 金 2009年10月限,1\\,078,11,2008/10/06,12:00:00\r\n")); } + @Test + public void testSaveQuote() throws Exception { + CsvConfig cfg = new CsvConfig(','); + cfg.setEscapeDisabled(false); + cfg.setNullString("NULL"); + cfg.setIgnoreTrailingWhitespaces(true); + cfg.setIgnoreLeadingWhitespaces(true); + cfg.setIgnoreEmptyLines(true); + cfg.setLineSeparator(Constants.CRLF); + cfg.setQuoteDisabled(false); + cfg.setQuotePolicy(QuotePolicy.COLUMN); + + final List list = new ArrayList(); + list.add(new SampleQuote(1, "aaa")); + list.add(new SampleQuote(2, "")); + list.add(new SampleQuote(3, null)); + list.add(new SampleQuote(4, "d\"d\"d")); + + final StringWriter sw = new StringWriter(); + final CsvWriter writer = new CsvWriter(sw, cfg); + try { + new CsvEntityListHandler(SampleQuote.class).save(list, writer); + } finally { + writer.close(); + } + assertThat(sw.getBuffer().toString(), is("No.,ラベル\r\n1,\"aaa\"\r\n2,\r\n3,NULL\r\n4,\"d\\\"d\\\"d\"\r\n")); + } + } \ No newline at end of file diff --git a/src/test/java/com/orangesignal/csv/io/CsvEntityWriterTest.java b/src/test/java/com/orangesignal/csv/io/CsvEntityWriterTest.java index 65586a2..fb8095c 100644 --- a/src/test/java/com/orangesignal/csv/io/CsvEntityWriterTest.java +++ b/src/test/java/com/orangesignal/csv/io/CsvEntityWriterTest.java @@ -35,13 +35,14 @@ import com.orangesignal.csv.Constants; import com.orangesignal.csv.CsvConfig; import com.orangesignal.csv.CsvWriter; +import com.orangesignal.csv.QuotePolicy; import com.orangesignal.csv.bean.CsvEntityTemplate; import com.orangesignal.csv.entity.DefaultValuePrice; import com.orangesignal.csv.entity.Price; -import com.orangesignal.csv.entity.Travel; import com.orangesignal.csv.entity.WritableEntity; import com.orangesignal.csv.entity.WritableNoHeaderEntity; import com.orangesignal.csv.filters.SimpleCsvNamedValueFilter; +import com.orangesignal.csv.model.SampleQuote; /** * {@link CsvEntityWriter} クラスの単体テストです。 @@ -590,6 +591,56 @@ public void testFilter() throws Exception { assertThat(sw.getBuffer().toString(), is("シンボル,名称,価格,出来高,日付,時刻\r\nGCV09,COMEX 金 2009年10月限,1\\,078,11,2008/10/06,12:00:00\r\n")); } + /** + * + public void testWrite() throws Exception { + final StringWriter sw = new StringWriter(); + final CsvEntityWriter writer = CsvEntityWriter.newInstance( + new CsvWriter(sw, cfg), + Price.class + ); + try { + final DateFormat df = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss"); + df.setTimeZone(TimeZone.getTimeZone("Asia/Tokyo")); + + writer.write(new Price("AAAA", "aaa", 10000, 10, df.parse("2008/10/28 10:24:00"))); + writer.write(new Price("BBBB", "bbb", null, 0, null)); + writer.write(new Price("CCCC", "ccc", 20000, 100, df.parse("2008/10/26 14:20:10"))); + } finally { + writer.close(); + } + assertThat(sw.getBuffer().toString(), is("シンボル,名称,価格,出来高,日付,時刻\r\nAAAA,aaa,10\\,000,10,2008/10/28,10:24:00\r\nBBBB,bbb,NULL,0,NULL,NULL\r\nCCCC,ccc,20\\,000,100,2008/10/26,14:20:10\r\n")); + } * + */ + + @Test + public void testWriteQuote() throws Exception { + CsvConfig cfg = new CsvConfig(','); + cfg.setEscapeDisabled(false); + cfg.setNullString("NULL"); + cfg.setIgnoreTrailingWhitespaces(true); + cfg.setIgnoreLeadingWhitespaces(true); + cfg.setIgnoreEmptyLines(true); + cfg.setLineSeparator(Constants.CRLF); + cfg.setQuoteDisabled(false); + cfg.setQuotePolicy(QuotePolicy.COLUMN); + + final StringWriter sw = new StringWriter(); + final CsvEntityWriter writer = CsvEntityWriter.newInstance( + new CsvWriter(sw, cfg), + SampleQuote.class + ); + try { + writer.write(new SampleQuote(1, "aaa")); + writer.write(new SampleQuote(2, "")); + writer.write(new SampleQuote(3, null)); + writer.write(new SampleQuote(4, "d\"d\"d")); + } finally { + writer.close(); + } + assertThat(sw.getBuffer().toString(), is("No.,ラベル\r\n1,\"aaa\"\r\n2,\r\n3,NULL\r\n4,\"d\\\"d\\\"d\"\r\n")); + } + // ------------------------------------------------------------------------ // getter / setter diff --git a/src/test/java/com/orangesignal/csv/model/SampleQuote.java b/src/test/java/com/orangesignal/csv/model/SampleQuote.java new file mode 100644 index 0000000..7b583bc --- /dev/null +++ b/src/test/java/com/orangesignal/csv/model/SampleQuote.java @@ -0,0 +1,38 @@ +/* + * Copyright 2010-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.orangesignal.csv.model; + +import com.orangesignal.csv.annotation.CsvColumn; +import com.orangesignal.csv.annotation.CsvEntity; + +/** + * @author Koichi Kobayashi + */ +@CsvEntity +public final class SampleQuote { + + @CsvColumn(position = 0, name = "No.") + public Integer no; + + @CsvColumn(position = 1, name = "ラベル", columnQuote = true) + public String label; + + public SampleQuote(Integer no, String label) { + this.no = no; + this.label = label; + } +} \ No newline at end of file From 5a58091506c82bdc155f63912189661d77e594bd Mon Sep 17 00:00:00 2001 From: Koichi-Kobayashi Date: Mon, 8 Jun 2015 09:53:21 +0900 Subject: [PATCH 2/2] =?UTF-8?q?CsvWirter=20=E3=81=A7=E6=9B=B8=E3=81=8D?= =?UTF-8?q?=E8=BE=BC=E3=82=80=E3=81=A8=E3=81=8D=E3=80=81=E9=A0=85=E7=9B=AE?= =?UTF-8?q?=E5=80=A4=E3=81=8C=20null=20=E3=81=AB=E5=8A=A0=E3=81=88?= =?UTF-8?q?=E3=81=A6=E7=A9=BA=E7=99=BD=E3=81=AE=E5=A0=B4=E5=90=88=E3=82=82?= =?UTF-8?q?=20NULL=20=E6=96=87=E5=AD=97=E5=88=97=E3=81=8C=E6=9C=89?= =?UTF-8?q?=E5=8A=B9=E3=81=A7=E3=81=82=E3=82=8C=E3=81=B0=20NULL=20?= =?UTF-8?q?=E6=96=87=E5=AD=97=E5=88=97=E3=81=B8=E7=BD=AE=E6=8F=9B=E3=81=88?= =?UTF-8?q?=E3=82=8B=E3=82=88=E3=81=86=E3=81=AB=E4=BF=AE=E6=AD=A3=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/com/orangesignal/csv/CsvWriter.java | 6 +++--- src/test/java/com/orangesignal/csv/CsvWriterTest.java | 2 +- .../orangesignal/csv/handlers/CsvEntityListHandlerTest.java | 2 +- .../java/com/orangesignal/csv/io/CsvEntityWriterTest.java | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/orangesignal/csv/CsvWriter.java b/src/main/java/com/orangesignal/csv/CsvWriter.java index 44d3c46..95d5f1a 100644 --- a/src/main/java/com/orangesignal/csv/CsvWriter.java +++ b/src/main/java/com/orangesignal/csv/CsvWriter.java @@ -190,9 +190,9 @@ private void writeValuesCore(final List values, List quotes) th String value = values.get(i); boolean enclose = false; // 項目を囲み文字で囲むかどうか - if (value == null) { - // 項目値が null の場合に NULL 文字列が有効であれば NULL 文字列へ置換えます。 - if (cfg.getNullString() == null) { + if (value == null || "".equals(value)) { + // 項目値が null もしくは空白の場合に NULL 文字列が有効であれば NULL 文字列へ置換えます。 + if (cfg.getNullString() == null) { continue; } value = cfg.getNullString(); diff --git a/src/test/java/com/orangesignal/csv/CsvWriterTest.java b/src/test/java/com/orangesignal/csv/CsvWriterTest.java index b5c71d6..e8c0d29 100644 --- a/src/test/java/com/orangesignal/csv/CsvWriterTest.java +++ b/src/test/java/com/orangesignal/csv/CsvWriterTest.java @@ -222,7 +222,7 @@ public void testWriteNoNQuote() throws IOException { writer.writeValues(Arrays.asList(new String[]{ "zzz", "", null })); writer.flush(); // Assert - assertThat(sw.getBuffer().toString(), is("aaa,\"b,\\\"b\",ccc\r\nzzz,,NULL\r\n")); + assertThat(sw.getBuffer().toString(), is("aaa,\"b,\\\"b\",ccc\r\nzzz,NULL,NULL\r\n")); } finally { writer.close(); } diff --git a/src/test/java/com/orangesignal/csv/handlers/CsvEntityListHandlerTest.java b/src/test/java/com/orangesignal/csv/handlers/CsvEntityListHandlerTest.java index f2815b0..439477c 100644 --- a/src/test/java/com/orangesignal/csv/handlers/CsvEntityListHandlerTest.java +++ b/src/test/java/com/orangesignal/csv/handlers/CsvEntityListHandlerTest.java @@ -244,7 +244,7 @@ public void testSaveQuote() throws Exception { } finally { writer.close(); } - assertThat(sw.getBuffer().toString(), is("No.,ラベル\r\n1,\"aaa\"\r\n2,\r\n3,NULL\r\n4,\"d\\\"d\\\"d\"\r\n")); + assertThat(sw.getBuffer().toString(), is("No.,ラベル\r\n1,\"aaa\"\r\n2,NULL\r\n3,NULL\r\n4,\"d\\\"d\\\"d\"\r\n")); } } \ No newline at end of file diff --git a/src/test/java/com/orangesignal/csv/io/CsvEntityWriterTest.java b/src/test/java/com/orangesignal/csv/io/CsvEntityWriterTest.java index fb8095c..0d6e904 100644 --- a/src/test/java/com/orangesignal/csv/io/CsvEntityWriterTest.java +++ b/src/test/java/com/orangesignal/csv/io/CsvEntityWriterTest.java @@ -638,7 +638,7 @@ public void testWriteQuote() throws Exception { } finally { writer.close(); } - assertThat(sw.getBuffer().toString(), is("No.,ラベル\r\n1,\"aaa\"\r\n2,\r\n3,NULL\r\n4,\"d\\\"d\\\"d\"\r\n")); + assertThat(sw.getBuffer().toString(), is("No.,ラベル\r\n1,\"aaa\"\r\n2,NULL\r\n3,NULL\r\n4,\"d\\\"d\\\"d\"\r\n")); } // ------------------------------------------------------------------------