使用ANTLR解析CSV和JSON
再续 ANTLR专题 ,有了前面的基础,下面开始用ANTLR
写一些有趣且实用的程序。
CSV
和JSON
这两种数据格式对软件开发人员来说最熟悉不过了,一般读写CSV
或JSON
格式的数据都会借助现成的、比较成熟工具库,非常方便。
试想一下,如果解析的是自定义格式的数据或者不依赖现有的CSV
、JSON
解析库,还有更通用的实现思路与解决方案吗?
ANTLR
作为一个专业且成熟的语言识别工具,就能提供一套通用的解决方案。
解析CSV
完整源码见: https://github.com/bytesfly/antlr-demo/tree/main/csv-loader/
输入CSV
格式数据:
Details,Month,Amount
Mid Bonus,June,"$2,000"
,January,"""zippo"""
Total Bonuses,"","$5,000"
解析后加载到内存中的数据结构是List<Map<String, String>>
,打印出来如下:
[{Month=June, Details=Mid Bonus, Amount="$2,000"}, {Month=January, Details=, Amount="""zippo"""}, {Month="", Details=Total Bonuses, Amount="$5,000"}]
该程序实现了对常见CSV
格式数据的解析。
语法规则为CSV.g4
,如下:
grammar CSV;
@header {package com.github.bytesfly.csvloader.antlr;}
file : header row+ ;
header : row ;
row : field (',' field)* NEWLINE ;
field
: TEXT # text
| STRING # string
| # empty
;
TEXT : ~[,\n\r"]+ ;
STRING : '"' ('""'|~'"')* '"' ; // 两个双引号是对双引号的转义
NEWLINE : '\r'? '\n' ;
上面的语法规则中,能明白为什么把header
和row
分开吗?
是为了解析时更简单方便,也更有助于理解。
我们自定义CsvLoaderListener.java
,如下:
public class CsvLoaderListener extends CSVBaseListener {
/**
* 存储表头字段
*/
private List<String> header;
/**
* 这个列表中的每个Map对应csv文件一行数据 ;
* Map是从字段名到字段值的映射
*/
private final List<Map<String, String>> rows = new ArrayList<>();
/**
* 存储正在读取的当前行的字段值
*/
private List<String> row;
@Override
public void exitHeader(CSVParser.HeaderContext ctx) {
header = row;
}
@Override
public void enterRow(CSVParser.RowContext ctx) {
row = new ArrayList<>();
}
@Override
public void exitRow(CSVParser.RowContext ctx) {
if (header != null) {
rows.add(CollUtil.zip(header, row));
}
}
@Override
public void exitText(CSVParser.TextContext ctx) {
row.add(ctx.TEXT().getText());
}
@Override
public void exitString(CSVParser.StringContext ctx) {
row.add(ctx.STRING().getText());
}
@Override
public void exitEmpty(CSVParser.EmptyContext ctx) {
row.add("");
}
public List<Map<String, String>> getRows() {
return rows;
}
}
最终完整的加载CSV
格式数据的程序为CsvLoader.java
,如下:
public class CsvLoader {
public static void main(String[] args) {
// 读取resources目录下example.csv文件
String s = FileUtil.readUtf8String("example.csv");
// 从字符串读取输入数据
CharStream input = CharStreams.fromString(s);
// 新建一个词法分析器
CSVLexer lexer = new CSVLexer(input);
// 新建一个词法符号的缓冲区,用于存储词法分析器将生成的词法符号
CommonTokenStream tokens = new CommonTokenStream(lexer);
// 新建一个语法分析器,处理词法符号缓冲区中的内容
CSVParser parser = new CSVParser(tokens);
// 针对file规则,开始语法分析
ParseTree tree = parser.file();
// 新建一个通用的、能够触发回调函数的语法分析树遍历器
ParseTreeWalker walker = new ParseTreeWalker();
// 创建我们自定义的监听器
CsvLoaderListener listener = new CsvLoaderListener();
// 遍历语法分析过程中生成的语法分析树,触发回调
walker.walk(listener, tree);
// 打印从csv文件加载的数据
System.out.println(listener.getRows());
}
}
解析JSON
完整源码见: https://github.com/bytesfly/antlr-demo/tree/main/json2xml/
输入JSON
格式的数据:
{
"description" : "An imaginary server config file",
"logs" : {"level":"verbose", "dir":"/var/log"},
"host" : "antlr.org",
"bool": true,
"null": null,
"pi": 3.14,
"admin": ["parrt", "tombu"],
"aliases": []
}
解析后并转成XML
格式数据如下:
<description>An imaginary server config file</description>
<logs>
<level>verbose</level>
<dir>/var/log</dir>
</logs>
<host>antlr.org</host>
<bool>true</bool>
<null>null</null>
<pi>3.14</pi>
<admin>
<element>parrt</element>
<element>tombu</element>
</admin>
<aliases>
</aliases>
该程序实现了对常见JSON
格式数据的解析并将其转成我们想要的XML
格式。
语法规则为JSON.g4
,如下:
// Derived from http://json.org
grammar JSON;
@header {package com.github.bytesfly.jx.antlr;}
json: object
| array
;
object
: '{' pair (',' pair)* '}' # AnObject
| '{' '}' # EmptyObject
;
array
: '[' value (',' value)* ']' # ArrayOfValues
| '[' ']' # EmptyArray
;
pair: STRING ':' value ;
value
: STRING # String
| NUMBER # Atom
| object # ObjectValue
| array # ArrayValue
| 'true' # Atom
| 'false' # Atom
| 'null' # Atom
;
LCURLY : '{' ;
LBRACK : '[' ;
STRING : '"' (ESC | ~["\\])* '"' ;
fragment ESC : '\\' (["\\/bfnrt] | UNICODE) ;
fragment UNICODE : 'u' HEX HEX HEX HEX ;
fragment HEX : [0-9a-fA-F] ;
NUMBER
: '-'? INT '.' INT EXP? // 1.35, 1.35E-9, 0.3, -4.5
| '-'? INT EXP // 1e10 -3e4
| '-'? INT // -3, 45
;
fragment INT : '0' | '1'..'9' '0'..'9'* ; // no leading zeros
fragment EXP : [Ee] [+\-]? INT ; // \- since - means "range" inside [...]
WS : [ \t\n\r]+ -> skip ;
我们自定义Json2XmlListener.java
,如下:
public class Json2XmlListener extends JSONBaseListener {
private final StringBuilder builder = new StringBuilder();
@Override
public void enterPair(JSONParser.PairContext ctx) {
// <key>
builder.append("<")
.append(stripQuotes(ctx.STRING().getText()))
.append(">");
}
@Override
public void exitPair(JSONParser.PairContext ctx) {
// </key>
builder.append("</")
.append(stripQuotes(ctx.STRING().getText()))
.append(">\n");
}
@Override
public void enterString(JSONParser.StringContext ctx) {
ifEnterArray(ctx);
builder.append(stripQuotes(ctx.STRING().getText()));
}
@Override
public void exitString(JSONParser.StringContext ctx) {
ifExitArray(ctx);
}
@Override
public void enterAtom(JSONParser.AtomContext ctx) {
ifEnterArray(ctx);
builder.append(ctx.getText());
}
@Override
public void exitAtom(JSONParser.AtomContext ctx) {
ifExitArray(ctx);
}
@Override
public void enterObjectValue(JSONParser.ObjectValueContext ctx) {
ifEnterArray(ctx);
builder.append("\n");
}
@Override
public void exitObjectValue(JSONParser.ObjectValueContext ctx) {
ifExitArray(ctx);
}
@Override
public void enterArrayValue(JSONParser.ArrayValueContext ctx) {
ifEnterArray(ctx);
builder.append("\n");
}
@Override
public void exitArrayValue(JSONParser.ArrayValueContext ctx) {
ifExitArray(ctx);
}
/**
* 去除字符串包裹着的双引号
*/
private static String stripQuotes(String s) {
if (s == null || s.charAt(0) != CharPool.DOUBLE_QUOTES) {
return s;
}
return s.substring(1, s.length() - 1);
}
/**
* 是否进入数组元素的访问
*/
private void ifEnterArray(JSONParser.ValueContext ctx) {
// 如果上级是数组的话
if (ctx.getParent().getRuleIndex() == JSONParser.RULE_array) {
builder.append("<element>");
}
}
/**
* 是否退出数组元素的访问
*/
private void ifExitArray(JSONParser.ValueContext ctx) {
// 如果上级是数组的话
if (ctx.getParent().getRuleIndex() == JSONParser.RULE_array) {
builder.append("</element>\n");
}
}
/**
* 获取JSON转XML的结果
*/
public String getResult() {
return builder.toString();
}
}
最终完整的解析JSON
并将其转成想要的XML
格式程序为Json2Xml.java
,如下:
public class Json2Xml {
public static void main(String[] args) {
// 读取resources目录下example.json文件
String s = FileUtil.readUtf8String("example.json");
// 从字符串读取输入数据
CharStream input = CharStreams.fromString(s);
// 新建一个词法分析器
JSONLexer lexer = new JSONLexer(input);
// 新建一个词法符号的缓冲区,用于存储词法分析器将生成的词法符号
CommonTokenStream tokens = new CommonTokenStream(lexer);
// 新建一个语法分析器,处理词法符号缓冲区中的内容
JSONParser parser = new JSONParser(tokens);
// 针对json规则,开始语法分析
ParseTree tree = parser.json();
// 新建一个通用的、能够触发回调函数的语法分析树遍历器
ParseTreeWalker walker = new ParseTreeWalker();
// 创建我们自定义的监听器
Json2XmlListener listener = new Json2XmlListener();
// 遍历语法分析过程中生成的语法分析树,触发回调
walker.walk(listener, tree);
// 打印JSON转XML的结果
System.out.println(listener.getResult());
}
}
通过上面两个实战案例,能感受到ANTLR
的威力嘛?
当然,别看自己写的代码不多,但是需要思考的地方并不少,不理解的地方还是建议自己下载源码本地打断点等方式琢磨琢磨,动手之后其实也不是太难。
使用ANTLR解析CSV和JSON的更多相关文章
- python cookbook第三版学习笔记七:python解析csv,json,xml文件
CSV文件读取: Csv文件格式如下:分别有2行三列. 访问代码如下: f=open(r'E:\py_prj\test.csv','rb') f_csv=csv.reader(f) for f in ...
- .NET 上传并解析CSV文件存库
1.前端: 放置浏览按钮 <div class="row inner_table text-center"> <input id="fileId&quo ...
- 一文综述python读写csv xml json文件各种骚操作
Python优越的灵活性和易用性使其成为最受欢迎的编程语言之一,尤其是对数据科学家而言.这在很大程度上是因为使用Python处理大型数据集是很简单的一件事情. 如今,每家科技公司都在制定数据战略. ...
- Python解析非标准JSON(Key值非字符串)
采集数据的时候经常碰到一些JSON数据的Key值不是字符串,这些数据在JavaScript的上下文中是可以解析的,但在Python中,没有该部分数据的上下文,无法采用json.loads(JSON)的 ...
- php解析.csv文件
public function actionImport() { //post请求过来的 $fileName = $_FILES['file']['name']; $fileTmpName = $_F ...
- Gson解析复杂的json数据
最近在给公司做一个直播APK的项目,主要就是通过解析网络服务器上的json数据,然后将频道地址下载下来再调用Android的播放器进行播放,原先本来打算使用普通的json解析方法即JsonObject ...
- 使用Gson解析复杂的json数据
Gson解析复杂的json数据 最近在给公司做一个直播APK的项目,主要就是通过解析网络服务器上的json数据,然后将频道地址下载下来再调用Android的播放器进行播放,原先本来打算使用普通的jso ...
- 正则表达式:根据逗号解析CSV并忽略引号内的逗号
需求:解析CSV文件并忽略引号内的逗号 解决方案: public static void main(String[] args) { String s = "a,b,c,\"1,0 ...
- C# 解析嵌套的json文件.
概述 今天我同学问我如何转换json文件,没处理过,网上搜了一下,json转excel的很少,反过来倒是有许多人写了工具. json文件的结构大致是这样的: {, , }, , "type& ...
随机推荐
- springboot和springcloud版本上的选择
现在的springboot项目和cloud版本都是更新很快,但我们开发不是版本越新越好,我们要把版本对应起来,那么我们怎么去关联呢? springboot和springcloud不是越新越好,clou ...
- Mac更换鼠标指针样式_ANI、CUR文件解析
前情提要 因为之前写了一篇mousecape的博客有一些回应,所以我决定写个续.主要是教大家怎么把cur文件和ani文件插入到mousecape里面,顺便提供几个做好的cape文件. 先给大家推荐一个 ...
- 【BZOJ 4668 冷战】
题目: [BZOJ 4668 冷战] 思路: 因为考虑强制在线,我们是肯定要维护形状的 我们发现如果\((u,v)\)这条边如果\(u,v\)已经连上,那么对于最终答案这条边是没有贡献的 所以我们发现 ...
- R包MetaboAnalystR安装指南(Linux环境非root)
前言 这是代谢组学数据分析的一个R包,包括用于代谢组学数据分析.可视化和功能注释等众多功能.最近有同事在集群中搭建蛋白和代谢流程,安装这个包出现了问题,于是我折腾了一上午. 这个包的介绍在:https ...
- EXCEL-批量删除筛选出的行,并且保留首行
筛选->ctrl+G->可见单元格->鼠标右键->删除整行. 之前的时候,是有个方法类似于上述步骤,可以保留标题行的,但是,不知道是不是少了哪一步,上述过程总是会删除标题行.就 ...
- EXCEL-COUNTIF()统计符合区间上的值个数
=COUNTIF(D9:D21465,"<-0.2")+COUNTIF(D9:D21465,">0.2") #计算<-0.2或者>0. ...
- 自动化测试系列(三)|UI测试
UI 测试是一种测试类型,也称为用户界面测试,通过该测试,我们检查应用程序的界面是否工作正常或是否存在任何妨碍用户行为且不符合书面规格的 BUG.了解用户将如何在用户和网站之间进行交互以执行 UI 测 ...
- ssm框架整合 — 更新完毕
1.spring整合mybatis 数据表自行搭建 ,我的结构如下: 1).导入依赖 <!-- spring整合mybatis的依赖 --> <!-- 1.spring需要的依赖 - ...
- 【c++】解析多文件编程的原理
其实一直搞不懂为什么头文件和其他cpp文件之间的关系,今晚索性一下整明白 [c++]解析多文件编程的原理 a.cpp #include<stdio.h> int main(){ a(); ...
- C++ 成绩排名
1004 成绩排名 (20分) 读入 n(>)名学生的姓名.学号.成绩,分别输出成绩最高和成绩最低学生的姓名和学号. 输入格式: 每个测试输入包含 1 个测试用例,格式为 第 1 行:正整数 ...