ANTLR4(七) 加载CSV数据
程序员文章站
2022-04-13 14:09:44
...
需求
目标
:将.csv文件的第一行作为列名,将接下来的每行的信息提取出来并打印。
输入t.csv
:输出
:
初步构想
我们将语法
和应用程序
部分解耦合。
语法部分
由于首行作为列名,我们需要将它与普通的行区分开。
每行的元素可以是String、text、甚至是空。
应用程序部分
保存首行的值作为列名。
创建一个保存所有行信息的List。
在进入改行时,每一行建立一个map,将首行的列名与值一个一个对应起来输入。
在离开该行时,将该行内容输出。
实际实现
语法部分
首先是row规则,‘\r’?
是为了判断有没有回车符,无论匹配与否,都换行。
TEXT词法规则中,由于, “ \n \r
都需要被实际匹配,因此标识符的匹配中不能有它们。
STRING词法规则中,我们用了‘ ”“ ’|~‘ ” ’
而不是‘ ”“ ’|‘ . ’
去匹配双引号中的双引号。这是因为非贪婪匹配的规则是 在满足匹配父规则情况下,尽可能少的匹配字符
。简单地说 “x""y"
在匹配时,我们希望匹配的是 x“”y
这一整个包含“”的字符串,而非贪婪匹配会使得我们匹配到“x”
和“y“
两个子字符串,因为显然 x
的字符数量要比 x“”y
少的多。所以,我们在"“中碰到单个"需要跳过,只有碰到”"才会匹配。
注意#text等标签:由于我们将调用Listener模式,而该模式本身只对文法规则设置exit和enter,我们需要对词法规则也加上标签。
grammar CSV;
file
: hdr row+
;
hdr
: row
;
row
: field (',' field)* '\r'? '\n'
;
field
: TEXT #text
| STRING #string
| #empty
;
TEXT
: ~[,"\n\r]+
;
STRING
: '"' ('""'|~'"')* '"'
;
应用程序部分
在初步构想中,我们的想法是接近的,但是逻辑不够严谨。
首先我们先将自定义Listener将会用到的数据结构定义一下:
- 创建一个List<map<String,String>>
rows
保存所有信息。 - 创建一个List
header
保存列名。 - 创建一个List
currentRowFieldValue
保存当前行的field的值。
然后从hdr规则入手,递归
地去考虑两种情况。
首行情况:
- enterHdr:什么都不做。
- enterRow: 实例化
currentRowFieldValue
。 - enterString enterText enterEmpty:什么都不做。
- exitstring exitText exitEmpty :添加每行的内容到
currentRowFieldValue
。 - exitRow:如果上层是
hdr
,直接return; - exitHdr:实例化
header
,将currentRowFieldValue
保存到header
中。
其它行情况:
- enterRow: 重新实例化
currentRowFieldValue
。 - enterString enterText enterEmpty:什么都不做。
- exitString exitText exitEmpty :添加内容到
currentRowFieldValue
。 - exitRow:如果不是,根据列名,一个个插入
rows
中。
编码细节:
- 注意
extends
(自定义Listener继承自BaseListener)和implements
(BaseListener实现了Listener接口)区别。
extends与implements区别 - 实例化List->
ArrayList
,实例化Map->LinkedHashMap
。 - 判断row的父结点是否为hdr:
ctx.getParent().getRuleIndex()==CSVParser.Rule_hdr
。 - Map添加是
put
,List添加是add
。
import org.antlr.v4.runtime.ANTLRInputStream;
import org.antlr.v4.runtime.CommonTokenStream;
import org.antlr.v4.runtime.tree.ParseTree;
import org.antlr.v4.runtime.tree.ParseTreeWalker;
import java.io.FileInputStream;
import java.io.InputStream;
import java.util.*;
public class LoadCSV {
public static class Loader extends CSVBaseListener {
public static final String EMPTY="";
List<Map<String,String>> rows=new ArrayList<Map<String,String>>();
List<String> header;
List<String> currentRowFieldValues;
public void exitHdr(CSVParser.HdrContext ctx) {
header=new ArrayList<String>();
header.addAll(currentRowFieldValues);
}
public void enterRow(CSVParser.RowContext ctx){
currentRowFieldValues=new ArrayList<String>();
}
public void exitString(CSVParser.StringContext ctx){
currentRowFieldValues.add(ctx.STRING().getText());
}
public void exitText(CSVParser.TextContext ctx){
currentRowFieldValues.add(ctx.TEXT().getText());
}
public void exitEmpty(CSVParser.EmptyContext ctx){
currentRowFieldValues.add(EMPTY);
}
public void exitRow(CSVParser.RowContext ctx){
if(ctx.getParent().getRuleIndex()==CSVParser.RULE_hdr){
return ;
}
else{
Map<String,String> m=new LinkedHashMap<String,String>();
int i=0;
for(String v:currentRowFieldValues){
m.put(header.get(i),v);
++i;
}
rows.add(m);
}
}
}
public static void main(String []args) throws Exception{
...
ParseTreeWalker walker=new ParseTreeWalker();
Loader loader=new Loader();
walker.walk(loader,tree);
System.out.println(loader.rows);
}
}