Skip to content

[Feature] add support for a custom CSVFormat #353

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 12 commits into from
Jun 23, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package cn.idev.excel.metadata.csv;

/**
* Constant definitions for CSV file processing.
*/
public class CsvConstant {
/**
* commonly used character
*/
public static final char SPACE = ' ';
public static final char BACKSLASH = '\\';
public static final char BACKSPACE = '\b';
public static final char PIPE = '|';
public static final char DOUBLE_QUOTE = '"';

/**
* line break
*/
public static final String CR = "\r";
public static final String FF = "\f";
public static final String LF = "\n";
public static final String CRLF = "\r\n";

/**
* field related
*/
public static final String TAB = "\t";
public static final String COMMA = ",";
public static final String EMPTY = "";
public static final String AT = "@";

/**
* unicode
*/
public static final String UNICODE_EMPTY = "\u0000";
public static final String UNICODE_NEX_LINE = "\u0085";
public static final String UNICODE_LINE_SEPARATOR = "\u2028";

/**
* database NULL value
*/
public static final String SQL_NULL_STRING = "\\N";

private CsvConstant() {
}
}
Original file line number Diff line number Diff line change
@@ -1,22 +1,13 @@
package cn.idev.excel.metadata.csv;

import java.io.Closeable;
import java.io.IOException;
import java.math.BigDecimal;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import cn.idev.excel.constant.BuiltinFormats;
import cn.idev.excel.enums.ByteOrderMarkEnum;
import cn.idev.excel.enums.NumericCellTypeEnum;
import cn.idev.excel.exception.ExcelGenerateException;
import cn.idev.excel.util.DateUtils;
import cn.idev.excel.util.ListUtils;
import cn.idev.excel.util.NumberDataFormatterUtils;
import cn.idev.excel.util.StringUtils;
import cn.idev.excel.constant.BuiltinFormats;

import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.Setter;
Expand Down Expand Up @@ -45,6 +36,14 @@
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.ss.util.PaneInformation;

import java.io.Closeable;
import java.io.IOException;
import java.math.BigDecimal;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

/**
* csv sheet
*
Expand Down Expand Up @@ -90,7 +89,7 @@ public CsvSheet(CsvWorkbook csvWorkbook, Appendable out) {
this.csvWorkbook = csvWorkbook;
this.out = out;
this.rowCacheCount = 100;
this.csvFormat = CSVFormat.DEFAULT;
this.csvFormat = csvWorkbook.getCsvFormat() == null ? CSVFormat.DEFAULT : csvWorkbook.getCsvFormat();
this.lastRowIndex = -1;
}

Expand Down Expand Up @@ -290,7 +289,7 @@ public List<CellRangeAddress> getMergedRegions() {

@Override
public Iterator<Row> rowIterator() {
return (Iterator<Row>)(Iterator<? extends Row>)rowCache.iterator();
return (Iterator<Row>) (Iterator<? extends Row>) rowCache.iterator();
}

@Override
Expand Down Expand Up @@ -761,7 +760,7 @@ public void flushData() {
Iterator<Cell> cellIterator = row.cellIterator();
int columnIndex = 0;
while (cellIterator.hasNext()) {
CsvCell csvCell = (CsvCell)cellIterator.next();
CsvCell csvCell = (CsvCell) cellIterator.next();
while (csvCell.getColumnIndex() > columnIndex++) {
csvPrinter.print(null);
}
Expand Down Expand Up @@ -807,7 +806,7 @@ private String buildCellValue(CsvCell csvCell) {
if (csvCell.getNumberValue() == null) {
return null;
}
//number
// number
if (dataFormat == null) {
dataFormat = BuiltinFormats.GENERAL;
dataFormatString = csvWorkbook.createDataFormat().getFormat(dataFormat);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,10 @@
package cn.idev.excel.metadata.csv;

import java.io.IOException;
import java.io.OutputStream;
import java.nio.charset.Charset;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;

import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.Setter;
import org.apache.commons.compress.utils.Lists;
import org.apache.commons.csv.CSVFormat;
import org.apache.poi.ss.SpreadsheetVersion;
import org.apache.poi.ss.formula.EvaluationWorkbook;
import org.apache.poi.ss.formula.udf.UDFFinder;
Expand All @@ -26,6 +20,13 @@
import org.apache.poi.ss.usermodel.SheetVisibility;
import org.apache.poi.ss.usermodel.Workbook;

import java.io.IOException;
import java.io.OutputStream;
import java.nio.charset.Charset;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;

/**
* csv workbook
*
Expand All @@ -44,7 +45,6 @@ public class CsvWorkbook implements Workbook {
* true if date uses 1904 windowing, or false if using 1900 date windowing.
* <p>
* default is false
*
*/
private Boolean use1904windowing;

Expand Down Expand Up @@ -83,9 +83,14 @@ public class CsvWorkbook implements Workbook {
* Default true.
*/
private Boolean withBom;

/**
* Specifies CSVFormat for parsing.
*/
private CSVFormat csvFormat;

public CsvWorkbook(Appendable out, Locale locale, Boolean use1904windowing, Boolean useScientificFormat,
Charset charset, Boolean withBom) {
Charset charset, Boolean withBom) {
this.out = out;
this.locale = locale;
this.use1904windowing = use1904windowing;
Expand Down Expand Up @@ -196,7 +201,7 @@ public Font createFont() {

@Override
public Font findFont(boolean bold, short color, short fontHeight, String name, boolean italic, boolean strikeout,
short typeOffset, byte underline) {
short typeOffset, byte underline) {
return null;
}

Expand All @@ -220,7 +225,7 @@ public CellStyle createCellStyle() {
if (csvCellStyleList == null) {
csvCellStyleList = Lists.newArrayList();
}
CsvCellStyle csvCellStyle = new CsvCellStyle((short)csvCellStyleList.size());
CsvCellStyle csvCellStyle = new CsvCellStyle((short) csvCellStyleList.size());
csvCellStyleList.add(csvCellStyle);
return csvCellStyle;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
package cn.idev.excel.read.builder;

import cn.idev.excel.ExcelReader;
import cn.idev.excel.event.SyncReadListener;
import cn.idev.excel.exception.ExcelGenerateException;
import cn.idev.excel.read.metadata.ReadSheet;
import cn.idev.excel.read.metadata.ReadWorkbook;
import cn.idev.excel.support.ExcelTypeEnum;
import org.apache.commons.csv.CSVFormat;
import org.apache.commons.csv.QuoteMode;

import java.util.List;

/**
* Builder for CSV file reading
*/
public class CsvReaderBuilder extends AbstractExcelReaderParameterBuilder<CsvReaderBuilder, ReadSheet> {
private ReadWorkbook readWorkbook;
private ReadSheet readSheet;
private CSVFormat.Builder csvFormatBuilder;

private CsvReaderBuilder() {
}

public CsvReaderBuilder(ReadWorkbook readWorkbook) {
readWorkbook.setExcelType(ExcelTypeEnum.CSV);
this.readWorkbook = readWorkbook;
this.readSheet = new ReadSheet();
this.csvFormatBuilder = CSVFormat.DEFAULT.builder();
}

/**
* Sets the delimiter character
*
* @param delimiter the delimiter character
* @return Returns a CsvReaderBuilder object, enabling method chaining
*/
public CsvReaderBuilder delimiter(String delimiter) {
if (delimiter != null) {
this.csvFormatBuilder.setDelimiter(delimiter);
}
return this;
}

/**
* Sets the quote character
*
* @param quote the quote character
* @return Returns a CsvReaderBuilder object, enabling method chaining
*/
public CsvReaderBuilder quote(Character quote) {
return quote(quote, QuoteMode.MINIMAL);
}

/**
* Sets the quote character and the quoting behavior
*
* @param quote the quote character
* @param quoteMode defines the quoting behavior
* @return Returns a CsvReaderBuilder object, enabling method chaining
*/
public CsvReaderBuilder quote(Character quote, QuoteMode quoteMode) {
if (quote != null) {
this.csvFormatBuilder.setQuote(quote);
}
if (quoteMode != null) {
this.csvFormatBuilder.setQuoteMode(quoteMode);
}
return this;
}

/**
* Sets the line separator
*
* @param recordSeparator the line separator
* @return Returns a CsvReaderBuilder object, enabling method chaining
*/
public CsvReaderBuilder recordSeparator(String recordSeparator) {
if (recordSeparator != null) {
this.csvFormatBuilder.setRecordSeparator(recordSeparator);
}
return this;
}

/**
* Sets the null string
*
* @param nullString the String to convert to and from {@code null}
* @return Returns a CsvReaderBuilder object, enabling method chaining
*/
public CsvReaderBuilder nullString(String nullString) {
if (nullString != null) {
this.csvFormatBuilder.setNullString(nullString);
}
return this;
}

/**
* Sets the escape character.
*
* @param escape the Character used to escape special characters in values
* @return Returns a CsvReaderBuilder object, enabling method chaining
*/
public CsvReaderBuilder escape(Character escape) {
if (escape != null) {
this.csvFormatBuilder.setEscape(escape);
}
return this;
}

private ExcelReader buildExcelReader() {
if (this.readWorkbook.getAutoTrim() != null) {
this.csvFormatBuilder.setTrim(this.readWorkbook.getAutoTrim());
}
if (this.readWorkbook.getIgnoreEmptyRow() != null) {
this.csvFormatBuilder.setIgnoreEmptyLines(this.readWorkbook.getIgnoreEmptyRow());
}
this.readWorkbook.setCsvFormat(this.csvFormatBuilder.build());
return new ExcelReader(this.readWorkbook);
}

public void doRead() {
if (this.readWorkbook == null) {
throw new ExcelGenerateException("Must use 'FastExcelFactory.read().csv()' to call this method");
}
ExcelReader excelReader = buildExcelReader();
excelReader.read(this.readSheet);
excelReader.finish();
}

/**
* synchronous read and returns the results
*
* @return Returns a list containing the read data
*/
public <T> List<T> doReadSync() {
if (this.readWorkbook == null) {
throw new ExcelGenerateException("Must use 'FastExcelFactory.read().csv()' to call this method");
}
ExcelReader excelReader = buildExcelReader();
// Register a synchronous read listener
SyncReadListener syncReadListener = new SyncReadListener();
registerReadListener(syncReadListener);
excelReader.read(this.readSheet);
excelReader.finish();
return (List<T>) syncReadListener.getList();
}

@Override
protected ReadSheet parameter() {
return this.readSheet;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,11 @@ public ExcelReaderSheetBuilder sheet(Integer sheetNo, String sheetName) {
return excelReaderSheetBuilder;
}

public CsvReaderBuilder csv() {
excelType(ExcelTypeEnum.CSV);
return new CsvReaderBuilder(parameter());
}

@Override
protected ReadWorkbook parameter() {
return readWorkbook;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.Setter;
import org.apache.commons.csv.CSVFormat;

import javax.xml.parsers.SAXParserFactory;
import java.io.File;
Expand Down Expand Up @@ -123,4 +124,10 @@ public class ReadWorkbook extends ReadBasicParameter {
* Ignore hidden sheet.
*/
private Boolean ignoreHiddenSheet;

/**
* Specifies CSVFormat for parsing.
* Only work on the CSV file.
*/
private CSVFormat csvFormat;
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,6 @@ public class CsvReadWorkbookHolder extends ReadWorkbookHolder {
public CsvReadWorkbookHolder(ReadWorkbook readWorkbook) {
super(readWorkbook);
setExcelType(ExcelTypeEnum.CSV);
this.csvFormat = CSVFormat.DEFAULT;
this.csvFormat = readWorkbook.getCsvFormat() == null ? CSVFormat.DEFAULT : readWorkbook.getCsvFormat();
}
}
Loading