在 Java 中,如何处理需要解析的 CSV 中的双引号
In Java, how do you deal with double quote inside of a CSV that you need to parse
这是我想做的,
这是我的 spend.csv 文件:
"Date","Description","Detail","Amount"
"5/03/21","Cinema","Batman","7.90"
"15/02/20","Groceries","Potatoes","23.00"
"9/12/21","DIY","Wood Plates","33.99"
"9/07/22","Fuel","Shell",".00"
"23/08/19","Lamborghini","Aventador","800,000.00"
来自 table 视图:
Table View of the csv
这就是我想要的输出文件 spend.xml :
<?xml version="1.0" encoding="UTF-8"?>
<SPEND>
<RECORD DATE="5/03/21">
<DESC>Cinema</DESC>
<DETAIL>Batman</DETAIL>
<AMOUNT>7.90</AMOUNT>
</RECORD>
<RECORD DATE="15/02/20">
<DESC>Groceries</DESC>
<DETAIL>Potatoes</DETAIL>
<AMOUNT>23.00</AMOUNT>
</RECORD>
<RECORD DATE="9/12/21">
<DESC>DIY</DESC>
<DETAIL>Wood Plates</DETAIL>
<AMOUNT>33.99</AMOUNT>
</RECORD>
<RECORD DATE="9/07/22">
<DESC>Fuel</DESC>
<DETAIL>Shell</DETAIL>
<AMOUNT>.00</AMOUNT>
</RECORD>
<RECORD DATE="23/08/19">
<DESC>Lamborghini</DESC>
<DETAIL>Aventador</DETAIL>
<AMOUNT>800,000.00</AMOUNT>
</RECORD>
</SPEND>
为了做到这一点,我在这里和那里找到了一些东西并设法得到了这个:
public class Main {
public static void main(String[] args) throws FileNotFoundException {
List<String> headers = new ArrayList<String>(5);
File file = new File("spend.csv");
BufferedReader reader = null;
try {
DocumentBuilderFactory domFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder domBuilder = domFactory.newDocumentBuilder();
Document newDoc = domBuilder.newDocument();
// Root element
Element rootElement = newDoc.createElement("XMLCreators");
newDoc.appendChild(rootElement);
reader = new BufferedReader(new FileReader(file));
int line = 0;
String text = null;
while ((text = reader.readLine()) != null) {
StringTokenizer st = new StringTokenizer(text, "", false);
int index = 0;
String[] rowValues = text.split(",");
if (line == 0) { // Header row
for (String col : rowValues) {
headers.add(col);
}
} else { // Data row
Element rowElement = newDoc.createElement("RECORDS");
rootElement.appendChild(rowElement);
for (int col = 0; col < headers.size(); col++) {
String header = headers.get(col);
String value = null;
if (col < rowValues.length) {
value = rowValues[col];
} else {
value = "";
}
Element curElement = newDoc.createElement(header);
curElement.appendChild(newDoc.createTextNode(value));
rowElement.appendChild(curElement);
}
}
line++;
}
ByteArrayOutputStream baos = null;
OutputStreamWriter osw = null;
try {
baos = new ByteArrayOutputStream();
osw = new OutputStreamWriter(baos);
TransformerFactory tranFactory = TransformerFactory.newInstance();
Transformer aTransformer = tranFactory.newTransformer();
aTransformer.setOutputProperty(OutputKeys.INDENT, "yes");
aTransformer.setOutputProperty(OutputKeys.METHOD, "xml");
aTransformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4");
Source src = new DOMSource(newDoc);
Result result = new StreamResult(osw);
aTransformer.transform(src, result);
osw.flush();
System.out.println(new String(baos.toByteArray()));
} catch (Exception exp) {
exp.printStackTrace();
} finally {
try {
osw.close();
} catch (Exception e) {
}
try {
baos.close();
} catch (Exception e) {
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
此时程序应该在终端中打印 XML 文件但是;
遗憾的是,由于我的 CSV 文件中每个值的双引号,我遇到了这个问题:
java org.w3c.dom.domexception invalid_character_err 指定了无效或非法的 xml 字符
我想我在这些行周围遗漏了一些东西:
StringTokenizer st = new StringTokenizer(text, "", false);
int index = 0;
String[] rowValues = text.split(",");
我想在我的 CSV 中保留双引号,如果有人有想法请随时告诉我!
在您 运行 转换之前,请执行
String.replaceAll("\"", "####")
然后运行转换,当转换完成后,将其反转并将字符串中的所有“####”替换为双引号
使用 OpenCsv 和 Jackson 的另一种可能方法:
public class FileProcessor {
public static void main(String[] args) throws IOException {
List<DataStructure> importList = new CsvToBeanBuilder<DataStructure>(
new FileReader("pathIn"))
.withIgnoreEmptyLine(true)
.withType(DataStructure.class)
.build()
.parse();
ListLoader exportList = new ListLoader(importList);
XmlMapper xmlMapper = new XmlMapper();
xmlMapper.configure(ToXmlGenerator.Feature.WRITE_XML_DECLARATION, true)
.enable(SerializationFeature.INDENT_OUTPUT)
.writeValue(new File("pathOut"), exportList);
}
}
Class 序列化每个元素:
@Data
public class DataStructure {
@CsvBindByName
@JacksonXmlProperty(isAttribute = true, localName = "DATE")
private String date;
@CsvBindByName
@JacksonXmlProperty(localName = "DESC")
private String description;
@CsvBindByName
@JacksonXmlProperty(localName = "DETAIL")
private String detail;
@CsvBindByName
@JacksonXmlProperty(localName = "AMOUNT")
private String amount;
}
Class 序列化完整列表:
@JacksonXmlRootElement(localName = "SPEND")
public class ListLoader {
@JacksonXmlElementWrapper(useWrapping = false)
@JacksonXmlProperty(localName = "RECORD")
private List<DataStructure> list;
public ListLoader(List<DataStructure> list){
this.list = list;
}
}
这是我想做的,
这是我的 spend.csv 文件:
"Date","Description","Detail","Amount"
"5/03/21","Cinema","Batman","7.90"
"15/02/20","Groceries","Potatoes","23.00"
"9/12/21","DIY","Wood Plates","33.99"
"9/07/22","Fuel","Shell",".00"
"23/08/19","Lamborghini","Aventador","800,000.00"
来自 table 视图:
Table View of the csv
这就是我想要的输出文件 spend.xml :
<?xml version="1.0" encoding="UTF-8"?>
<SPEND>
<RECORD DATE="5/03/21">
<DESC>Cinema</DESC>
<DETAIL>Batman</DETAIL>
<AMOUNT>7.90</AMOUNT>
</RECORD>
<RECORD DATE="15/02/20">
<DESC>Groceries</DESC>
<DETAIL>Potatoes</DETAIL>
<AMOUNT>23.00</AMOUNT>
</RECORD>
<RECORD DATE="9/12/21">
<DESC>DIY</DESC>
<DETAIL>Wood Plates</DETAIL>
<AMOUNT>33.99</AMOUNT>
</RECORD>
<RECORD DATE="9/07/22">
<DESC>Fuel</DESC>
<DETAIL>Shell</DETAIL>
<AMOUNT>.00</AMOUNT>
</RECORD>
<RECORD DATE="23/08/19">
<DESC>Lamborghini</DESC>
<DETAIL>Aventador</DETAIL>
<AMOUNT>800,000.00</AMOUNT>
</RECORD>
</SPEND>
为了做到这一点,我在这里和那里找到了一些东西并设法得到了这个:
public class Main {
public static void main(String[] args) throws FileNotFoundException {
List<String> headers = new ArrayList<String>(5);
File file = new File("spend.csv");
BufferedReader reader = null;
try {
DocumentBuilderFactory domFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder domBuilder = domFactory.newDocumentBuilder();
Document newDoc = domBuilder.newDocument();
// Root element
Element rootElement = newDoc.createElement("XMLCreators");
newDoc.appendChild(rootElement);
reader = new BufferedReader(new FileReader(file));
int line = 0;
String text = null;
while ((text = reader.readLine()) != null) {
StringTokenizer st = new StringTokenizer(text, "", false);
int index = 0;
String[] rowValues = text.split(",");
if (line == 0) { // Header row
for (String col : rowValues) {
headers.add(col);
}
} else { // Data row
Element rowElement = newDoc.createElement("RECORDS");
rootElement.appendChild(rowElement);
for (int col = 0; col < headers.size(); col++) {
String header = headers.get(col);
String value = null;
if (col < rowValues.length) {
value = rowValues[col];
} else {
value = "";
}
Element curElement = newDoc.createElement(header);
curElement.appendChild(newDoc.createTextNode(value));
rowElement.appendChild(curElement);
}
}
line++;
}
ByteArrayOutputStream baos = null;
OutputStreamWriter osw = null;
try {
baos = new ByteArrayOutputStream();
osw = new OutputStreamWriter(baos);
TransformerFactory tranFactory = TransformerFactory.newInstance();
Transformer aTransformer = tranFactory.newTransformer();
aTransformer.setOutputProperty(OutputKeys.INDENT, "yes");
aTransformer.setOutputProperty(OutputKeys.METHOD, "xml");
aTransformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4");
Source src = new DOMSource(newDoc);
Result result = new StreamResult(osw);
aTransformer.transform(src, result);
osw.flush();
System.out.println(new String(baos.toByteArray()));
} catch (Exception exp) {
exp.printStackTrace();
} finally {
try {
osw.close();
} catch (Exception e) {
}
try {
baos.close();
} catch (Exception e) {
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
此时程序应该在终端中打印 XML 文件但是;
遗憾的是,由于我的 CSV 文件中每个值的双引号,我遇到了这个问题:
java org.w3c.dom.domexception invalid_character_err 指定了无效或非法的 xml 字符
我想我在这些行周围遗漏了一些东西:
StringTokenizer st = new StringTokenizer(text, "", false);
int index = 0;
String[] rowValues = text.split(",");
我想在我的 CSV 中保留双引号,如果有人有想法请随时告诉我!
在您 运行 转换之前,请执行
String.replaceAll("\"", "####")
然后运行转换,当转换完成后,将其反转并将字符串中的所有“####”替换为双引号
使用 OpenCsv 和 Jackson 的另一种可能方法:
public class FileProcessor {
public static void main(String[] args) throws IOException {
List<DataStructure> importList = new CsvToBeanBuilder<DataStructure>(
new FileReader("pathIn"))
.withIgnoreEmptyLine(true)
.withType(DataStructure.class)
.build()
.parse();
ListLoader exportList = new ListLoader(importList);
XmlMapper xmlMapper = new XmlMapper();
xmlMapper.configure(ToXmlGenerator.Feature.WRITE_XML_DECLARATION, true)
.enable(SerializationFeature.INDENT_OUTPUT)
.writeValue(new File("pathOut"), exportList);
}
}
Class 序列化每个元素:
@Data
public class DataStructure {
@CsvBindByName
@JacksonXmlProperty(isAttribute = true, localName = "DATE")
private String date;
@CsvBindByName
@JacksonXmlProperty(localName = "DESC")
private String description;
@CsvBindByName
@JacksonXmlProperty(localName = "DETAIL")
private String detail;
@CsvBindByName
@JacksonXmlProperty(localName = "AMOUNT")
private String amount;
}
Class 序列化完整列表:
@JacksonXmlRootElement(localName = "SPEND")
public class ListLoader {
@JacksonXmlElementWrapper(useWrapping = false)
@JacksonXmlProperty(localName = "RECORD")
private List<DataStructure> list;
public ListLoader(List<DataStructure> list){
this.list = list;
}
}