35b3c732创建于 2025年5月15日历史提交

csv4cj 库

介绍

csv 文件的仓颉操作工具,支持 csv 文件的读写、解析,支持中文。

1 仓颉语言支持 csv 文件的读取、解析

场景:

  1. OHOS, Linux, windows平台下可读取和解析csv 文件,支持中文
  2. 支持对 csv 文件格式的配置 约束: 性能:支持版本几何性能持平 可靠性: NA

1.1 csv支持不同格式的解析

CSV内容解析格式

1.1.1 主要接口
public class CSVParseFormat {
    
    /*
     * 默认的解析格式
     */
    public static prop let DEFAULT: CSVParseFormat

    /*
     * 设置解析格式的转义字符
     *
     * 参数 escapeCharacter - 转义字符 
     *
     * 返回值 CSVParseFormat- 返回对象本身
     */
    public func setEscapeCharacter(escapeCharacter: Rune): CSVParseFormat

    /*
     * 读取解析格式的转义字符
     *
     * 返回值 Rune - 解析格式的转义字符
     */
    public func getEscapeCharacter(): Rune

    /*
     * 设置是否将第一行做为标题行
     *
     * 参数 firstLineAsHeader - 是否第一行将第一行做为标题行
     *
     * 返回值 CSVParseFormat- 返回对象本身
     */
    public func setFirstLineAsHeader(firstLineAsHeader: Bool): CSVParseFormat

    /*
     * 读取是否将第一行做为标题行的设置
     *
     * 返回值 Bool - 是否将第一行做为标题行
     */
    public func getFirstLineAsHeader(): Bool

    /*
     * 设置解析格式的列标题
     *
     * 参数 headerList - 表示列标题的集合 
     *
     * 返回值 CSVParseFormat- 返回对象本身
     */
    public func setHeader(headerList: Collection<String>): CSVParseFormat

     /*
     * 读取解析格式的列标题数组
     *
     * 返回值 ArrayList<String> - 解析格式的列标题数组
     */
    public func getHeader(): ArrayList<String>

    /*
     * 设置是否忽略空行
     *
     * 参数 ignoreEmptyLines - 是否忽略空行 
     *
     * 返回值 CSVParseFormat- 返回对象本身
     */
    public func setIgnoreEmptyLines(ignoreEmptyLines: Bool): CSVParseFormat

     /*
     * 读取是否忽略空行的设置
     *
     * 返回值 Bool - 返回是否忽略空行的设置
     */ 
    public func getIgnoreEmptyLines(): Bool

    /*
     * 设置是否忽略内容前后的空白字符
     *
     * 参数 ignoreSurroundingSpaces - 是否忽略内容前后的空白字符 
     *
     * 返回值 CSVParseFormat- 返回对象本身
     */
    public func setIgnoreSurroundingSpaces(ignoreSurroundingSpaces: Bool): CSVParseFormat

     /*
     * 读取是否忽略内容前后的空白字符设置
     *
     * 返回值 Bool - 是否忽略内容前后的空白字符的设置
     */ 
    public func getIgnoreSurroundingSpaces(): Bool

    /*
     * 设置解析格式的引用字符
     *
     * 参数 quoteCharacter - 引用字符 
     *
     * 返回值 CSVParseFormat- 返回对象本身
     */
    public func setQuoteCharacter(quoteCharacter: Rune)

     /*
     * 读取解析格式的引用字符
     *
     * 返回值 Rune - 解析格式的引用字符
     */ 
    public func getQuoteCharacter(): Rune

    /*
     * 设置是否跳过标题行
     *
     * 参数 skipHeaderRecord - 是否跳过标题行 
     *
     * 返回值 CSVParseFormat- 返回对象本身
     */
    public func setSkipHeaderRecord(skipHeaderRecord: Bool): CSVParseFormat

     /*
     * 读取是否跳过标题行设置
     *
     * 返回值 Bool - 是否跳过标题行的设置
     */ 
    public func getSkipHeaderRecord(): Bool

    /*
     * 设置是否去除尾部分隔符
     *
     * 参数 trailingDelimiter - 是否去除尾部分隔符 
     *
     * 返回值 CSVParseFormat- 返回对象本身
     */
    public func setTrailingDelimiter(trailingDelimiter: Bool): CSVParseFormat

     /*
     * 读取是否去除尾部分隔符的设置
     *
     * 返回值 Bool - 是否去除尾部分隔符的设置
     */ 
    public func getTrailingDelimiter(): Bool

    /*
     * 设置解析格式的分隔符
     *
     * 参数 delimiter - 分隔符 
     *
     * 返回值 CSVParseFormat- 返回对象本身
     */
    public func setDelimiter(delimiter: String): CSVParseFormat

     /*
     * 读取解析格式的分隔符
     *
     * 返回值 String - 解析格式的分隔符
     */ 
    public func getDelimiterString(): String

    /*
     * 设置解析格式的注释符
     *
     * 参数 commentMarker - 注释符 
     *
     * 返回值 CSVParseFormat- 返回对象本身
     */
    public func setCommentMarker(commentMarker: Rune): CSVParseFormat

     /*
     * 读取解析格式的注释符
     *
     * 返回值 Rune - 解析格式的注释符
     */ 
    public func getCommentMarker(): Rune

}

/*
  * 判断当前 Option<T> 是否无值
  *
  * 参数 item - Option 值
  *
  * 返回值 Bool - Option 无值返回 true 否则返回 false
  */ 
public func isNone<T>(item: Option<T>): Bool

/*
  * 判断当前 Option<T> 是否有值
  *
  * 参数 item - Option 值
  *
  * 返回值 Bool - Option 有值返回 true 否则返回 false
  */ 
public func isSome<T>(item: Option<T>): Bool
1.1.2 示例
import std.collection.*
import csv4cj.*

main() {
    let x: Rune = '|'
    let f: CSVParseFormat = CSVParseFormat.DEFAULT.setEscapeCharacter(x)
    println("${f.getEscapeCharacter()}")
    
    let h: Array<String> = ["hello"]
    f.setHeader(h)
    println("${f.getHeader().toString()}")

    f.setIgnoreEmptyLines(true)
    println("${f.getIgnoreEmptyLines()}")

    f.setIgnoreSurroundingSpaces(true)
    println("${f.getIgnoreSurroundingSpaces()}")

    let c: Rune = '"'
    f.setQuoteCharacter(c)
    println("${f.getQuoteCharacter().toString()}")

    f.setTrailingDelimiter(true)
    println("${f.getTrailingDelimiter()}")

    let c1: Rune = '#'
    f.setCommentMarker(c1)
    println("${f.getCommentMarker().toString()}")

    f.setDelimiter("---")
    println("${f.getDelimiterString()}")
    return 0
}

1.2 CSV解析器

1.2.1 主要接口
public class CSVParser <: Iterable<CSVRecord> {
    
    /*
     * 主构造函数
     *
     * 参数 reader - csv内容读取对象 
     * 参数 format - 内容解析配置 
     */
    public CSVParser(reader: CSVReader, format: CSVParseFormat) 

    /*
     * 解析下一条记录, 解析失败则抛 Exception 异常
     *
     * 返回值 :Option<CSVRecord> - 下一条记录
     */
    public func nextRecord(): Option<CSVRecord>

    /*
     * 获取标题和序号的HashMap,其中key为标题,value为序号
     *
     * 返回值 :HashMap<String, Int64> - 标题的HashMap
     */
    public func getHeaderDict(): HashMap<String, Int64>

    /*
     * 获取标题的注释列表
     *
     * 返回值 :ArrayList<String> - 标题的注释列表
     */
    public func getHeaderComment(): ArrayList<String>

    /*
     * 获取当前解析的行序号
     *
     * 返回值 :Int64- 行序号
     */
    public func getCurrentLineNumber(): Int64

    /*
     * 获取行的迭代器
     *
     * 返回值 :Iterator<CSVRecord>- 行迭代器
     */
    public func iterator(): Iterator<CSVRecord> 

    /*
     * 获取解析后的CSVRecord对象列表
     *
     * 返回值 :ArrayList<CSVRecord>- 解析后的CSVRecord对象列表
     */
    public func getRecords(): ArrayList<CSVRecord>

    /*
     * 解析当前所有记录
     *
     * 返回值 :ArrayList<CSVRecord>- 解析后的CSVRecord对象列表
     */
    public func parseRecordsToEnd(): ArrayList<CSVRecord>
}

/*
  * 定义 Constants 常量类
  */
public class Constants{
    // 反斜线
    public static let BACKSLASH: Rune

    // 退格
    public static let BACKSPACE: Rune

    // 逗号
    public static let COMMA: Rune

    // 井号
    public static let COMMENT_CHAR: Rune

    // 回车
    public static let CR: Rune

    // 换行
    public static let LF: Rune

    //双引号
    public static let DOUBLE_QUOTE_CHAR: Rune

    // 换页
    public static let FF: Rune

    // 空格
    public static let SP: Rune

    // 制表符
    public static let TAB: Rune

    // 空字符
    public static let DISABLED: Rune: Rune

    // 管道符号
    public static let PIPE: Rune

    //记录分隔符
    public static let RS: Rune

    // 回车换行字符串
    public static let CRLF: String

    // 换行字符串
    public static let LF_STRING: String

    // 逗号字符串
    public static let COMMA_STRING: String

    // 回车字符串
    public static let CR_STRING: String

    // 空字符串
    public static let EMPTY: String
}
1.2.2 示例

示例 test.csv 文件如下:

id,name,age,remark
30020,biboo,28,middle school student
30021,张飞,45,三国演义男4号𥻗𪧘 
#这是第3行的注释,里面有转义的tab键和引用符号
30022,关\t羽,48,著名的武财神αβγ
30023,刘备,50,"胳膊比较长,善于编草鞋"

示例代码如下:

import std.fs.*
import stdx.serialization.serialization.*
import stdx.encoding.json.*
import std.collection.*
import std.posix.*
import csv4cj.*

main() {
    let path: String = getcwd()

    println("自定义输出:")
    customPrintingDemo("${path}/test.csv")
    parseRecordsToEnd()
    return 0
}

func parseRecordsToEnd(): Unit {
    let csvContent = 
        ###"Dan Simmons,Hyperion,"1989"            
# Comment Line 1
# Comment Line 2
# Comment Line 3
Douglas Adams,The Hitchhiker's "Guide" to the Galaxy,1979
Douglas John,The Hitchhiker's "Guide" to the Mars,1979"###

    let format = CSVParseFormat.DEFAULT
    let csvReader = CSVReader(StringStream(csvContent))
    let csvParser = CSVParser(csvReader, format)
    var csvRecordList = csvParser.parseRecordsToEnd()
}

//自定义输出格式
func customPrintingDemo(fileName: String) {
    //创建文件
    let fileStream = File(fileName, OpenMode.Read)

    if (fileStream.canRead()) {
        //创建字符读取的解析流
        let stream = UTF8ReaderStream(fileStream)
        let reader = CSVReader(stream)

        //创建格式化的解析参数
        let format: CSVParseFormat = CSVParseFormat.DEFAULT.setSkipHeaderRecord(true).setFirstLineAsHeader(false)

        //创建解析器
        let csvParser = CSVParser(reader, format)
        println(csvParser.getHeaderComment().toString())
        println(csvParser.getCurrentLineNumber())

        //遍历每一行解析记录
        for (csvRecord in csvParser) {
            let rowNo = csvRecord.getRecordNumber()

            //使用表头标题获取对应列的值
            let id = csvRecord.get("id", csvParser.getHeaderDict()) ?? ""
            let name = csvRecord.get("name", csvParser.getHeaderDict()) ?? ""

            //使用列序号获取对应列的值
            let age = csvRecord.get(2)
            let remark = csvRecord.get(3)

            if (let Some(comment) = csvRecord.getComment()) {
                println("RowNo:${rowNo}|Id:${id}|name:${name}|age:${age}|remark:${remark}|comment:${comment}")
            } else {
                println("RowNo:${rowNo}|Id:${id}|name:${name}|age:${age}|remark:${remark}")
            }
        }
        fileStream.close()
    }
}

1.3 csv逐行解析

读取CSV内容的类

1.3.1 主要接口
public class CSVReader{
    
    /*
     * 构造函数
     *
     * 参数 reader - 实现了CharReader接口的按字符读取对象 
     */
    public init(reader: CharReader)

    /*
     * 获取最后读取的字符
     *
     * 返回值 :Option<Rune> - 最后读取的字符
     */
    public func getLastChar(): Option<Rune> 

    /*
     * 是否到了流的结束位置
     *
     * 返回值 :Bool - 是否是流的结束位置
     */
    public func getIsEndOfStream(): Bool

    /*
     * 获取当前行号
     * 当未开始读取内容或者开始读取但是内容为空,返回0
     * 当开始读取第一行内容并且内容不为空时,返回1
     * 当读取完第一行的换行或者回车换行时,返回1
     * 当开始读取第二行并且读取到内容时,返回2,以此类推
     * 调用readLine函数不影响行号,该函数主要用来读取注释使用
     * 返回值 :Int64 - 当前行号
     */
    public func getCurrentLineNumber(): Int64

    /*
     * 读取下一个字符
     *
     * 返回值 :Option<Rune> - 下一个字符
     */
    public func read(): Option<Rune>

    /*
     * 读取后面的字符到字符数组buf,最大读取字符数量为buf的size
     *
     * 参数 buf - 存储读取内容的字符数组 
     *
     * 返回值 :Int64 - 成功读取的字符数
     */
    public func read(buf: Array<Rune>): Int64

    /*
     * 读取后面的字符到字符数组buf,最大读取字符数量为count,存储到buf时,从offset位置开始
     *
     * 参数 buf - 存储读取内容的字符数组 
     * 参数 offset - buf开始存储读取内容的位置
     * 参数 count - 最大读取字符数量
     *
     * 返回值 :Int64 - 成功读取的字符数
     */
    public func read(buf: Array<Rune>, offset: Int64, count: Int64): Int64 

    /*
     * 读取一行
     *
     * 返回值 :Option<String> - 读取的内容
     */
    public func readLine(): Option<String>

    /*
     * 读取后面的字符到字符数组buf,最大读取字符数量为buf的size,本次读取不改变流的当前位置
     *
     * 返回值 :Int64 - 成功读取的字符数
     */
    public func lookAhead(buf: Array<Rune>): Int64

    /*
     * 查看下一个字符,该动作不会改变要读取的流的当前位置
     *
     * 返回值 :Option<Rune> - 要读取的下一个字符
     */
    public func lookAhead(): Option<Rune> 

}

/*
 * 定义 CharReader 接口
 */
public interface CharReader {
    /*
     * 读取一个字符,该动作将会改变要读取的流的当前位置
     *
     * 返回值 Option<Rune> - 读取的字符
     */
    func read(): Option<Rune>

    /*
     * 读取后面的字符到字符数组buf,读取的最大数量为buf的size
     * 该动作不会改变要读取的流的当前位置
     *
     * 参数 buf - 存储读取出字符的字符数组对象
     *
     * 返回值 Int64 - 成功读取的字符数量
     */
    func lookAhead(buf: Array<Rune>): Int64

    /*
     * 查看下一个字符
     * 该动作不会改变要读取的流的当前位置
     *
     * 返回值 Option<Rune> - 要读取的下一个字符
     */
    func lookAhead(): Option<Rune>
}

/*
  * 从字符串中读取字符的类,该类实现了CharReader接口
  */
public class StringStream <: CharReader {
    /*
     * 主构造函数
     *
     * 参数 value - 要读取的字符串
     */
    public StringStream(value: String) 

    /*
     * 从字符串读取下一个字符
     *
     * 返回值 Option<Rune> - 读取的字符
     */
    public func read(): Option<Rune>

    /*
     * 从字符串读取后面的字符到字符数组buf,读取的最大数量为buf的size
     * 该动作不会改变要读取的字符串的当前位置
     *
     * 参数 buf - 存储读取出字符的字符数组对象
     *
     * 返回值 Int64 - 成功读取的字符数量
     */
    public func lookAhead(buf: Array<Rune>): Int64

    /*
     * 查看下一个字符
     * 该动作不会改变要读取的字符串的当前位置
     *
     * 返回值 Option<Rune> - 要读取的下一个字符
     */
    public func lookAhead(): Option<Rune>
}
1.3.2 示例

示例 test.csv 文件如下:

id,name,age,remark
30020,biboo,28,middle school student
30021,张飞,45,三国演义男4号𥻗𪧘 
#这是第3行的注释,里面有转义的tab键和引用符号
30022,关\t羽,48,著名的武财神αβγ
30023,刘备,50,"胳膊比较长,善于编草鞋"

示例代码如下:

import std.fs.*
import std.collection.*
import std.posix.*
import csv4cj.*

main() {
    let path: String = getcwd()
    let fileStream = File("${path}/test.csv", OpenMode.Read)
    if (fileStream.canRead()) {
        let s = UTF8ReaderStream(fileStream)
        let r = CSVReader(s)
        var arr: Array<Rune> = Array<Rune>(1024, item: Rune(0))
        println(r.read(arr, 0, 10))
        r.getCurrentLineNumber()
        println(r.getIsEndOfStream())
        fileStream.close()
    }

    testReadLine()
    return 0
}

func testReadLine(): Unit {
    var content = ""
    var readerStream = StringStream(content)

    var reader = CSVReader(readerStream)

    var line = reader.readLine()
    if (isNone(line)) {
        println("none")
    }

    println(isReadLineMatch("abc", "abc"))

    println(isReadLineMatch("你好\n仓颉", "你好"))

    println(isReadLineMatch("cangjie\r仓颉", "cangjie"))

    println(isReadLineMatch("Hello\r\n仓颉", "Hello"))
}

func isReadLineMatch(content: String, matchValue: String): Bool {
    var readerStream = StringStream(content)
    var reader = CSVReader(readerStream)
    var line = reader.readLine()
    if (let Some(value) = line) {
        return value == matchValue
    }

    return false
}

1.4 中文支持

使用UTF8编码进行内容读取的类,调用方需要保证文件的正确编码格式,否则可能得不到期望的解析结果

1.4.1 主要接口
public class UTF8ReaderStream <: CharReader{
    
    /*
     * 构造函数
     *
     * 参数 s - 要读取的文件,调用方需要保证文件的正确编码格式,否则可能得不到期望的解析结果
     */
    public init(s: File) 

    /*
     * 读取下一个字符
     *
     * 返回值 :Option<Rune> - 下一个字符
     */
    public func read(): Option<Rune>

    /*
     * 读取后面的字符到字符数组buf,最大读取字符数量为buf的size,本次读取不改变流的当前位置
     *
     * 返回值 :Int64 - 成功读取的字符数
     */
    public func lookAhead(buf: Array<Rune>): Int64

    /*
     * 查看下一个字符,该动作不会改变要读取的流的当前位置
     *
     * 返回值 :Option<Rune> - 要读取的下一个字符
     */
    public func lookAhead(): Option<Rune> 
}
1.4.2 示例

示例 test.csv 文件如下:

id,name,age,remark
30020,biboo,28,middle school student
30021,张飞,45,三国演义男4号𥻗𪧘 
#这是第3行的注释,里面有转义的tab键和引用符号
30022,关\t羽,48,著名的武财神αβγ
30023,刘备,50,"胳膊比较长,善于编草鞋"

示例代码如下:

import std.fs.*
import std.collection.*
import std.posix.*
import csv4cj.*

main() {
    let path: String = getcwd()
    let fileStream = File("${path}/test.csv", OpenMode.Read)
    if (fileStream.canRead()) {
        let s = UTF8ReaderStream(fileStream)
        s.lookAhead(Array<Rune>(1024,item:Rune(0)))
        
        let format = CSVParseFormat.DEFAULT
        let csvReader = CSVReader(s)
        let csvParser = CSVParser(csvReader, format)
        var csvRecord = csvParser.nextRecord()

        let outFormat = CSVOutFormat.DEFAULT.setQuoteMode(QuoteMode.None)
        let csvPrint = CSVPrinter(outFormat)
        let sb = StringBuilder()

        if (let Some(record) = csvRecord) {
            csvPrint.print(record, sb)
            csvRecord = csvParser.nextRecord()
        }
        fileStream.close()
    }

    return 0
}

2 仓颉语言支持将结构化的csv数据输出成cvs文件

场景:

  1. OHOS, Linux, windows平台下支持将结构化的 csv 记录输出 csv 文件,支持中文 约束:
  2. 性能:支持版本几何性能持平 可靠性: NA

2.1 CSV输出格式

2.1.1 主要接口
public class CSVOutFormat {
    
    /*
     * 默认的输出格式
     */
    public static prop let DEFAULT: CSVOutFormat

    /*
     * 设置输出格式的转义字符
     *
     * 参数 escapeCharacter - 转义字符 
     *
     * 返回值 CSVOutFormat - 返回对象本身
     */
    public func setEscapeCharacter(escapeCharacter: Rune): CSVOutFormat

    /*
     * 读取输出格式的转义字符
     *
     * 返回值 Rune - 输出格式的转义字符
     */
    public func getEscapeCharacter(): Rune

    /*
     * 设置输出格式的列标题
     *
     * 参数 headerList - 表示列标题的数组 
     *
     * 返回值 CSVOutFormat - 返回对象本身
     */
    public func setHeader(headerList: Array<String>): CSVOutFormat

     /*
     * 读取输出格式的列标题数组
     *
     * 返回值 Array<String> - 输出格式的列标题数组
     */
    public func getHeader(): Array<String>

    /*
     * 设置是否忽略空行
     *
     * 参数 ignoreEmptyRecord - 是否忽略空行 
     *
     * 返回值 CSVOutFormat - 返回对象本身
     */
    public func setIgnoreEmptyRecord(ignoreEmptyRecord: Bool): CSVOutFormat

     /*
     * 读取是否忽略空行的设置
     *
     * 返回值 Bool - 返回是否忽略空行的设置
     */ 
    public func getIgnoreEmptyRecord(): Bool

    /*
     * 设置是否忽略内容前后的空白字符
     *
     * 参数 trim - 是否忽略内容前后的空白字符 
     *
     * 返回值 CSVOutFormat - 返回对象本身
     */
    public func setTrim(trim: Bool): CSVOutFormat

     /*
     * 读取是否忽略内容前后的空白字符设置
     *
     * 返回值 Bool - 是否忽略内容前后的空白字符的设置
     */ 
    public func getTrim(): Bool

    /*
     * 设置输出格式的引用字符
     *
     * 参数 quoteCharacter - 引用字符 
     *
     * 返回值 CSVOutFormat - 返回对象本身
     */
    public func setQuoteCharacter(quoteCharacter: Rune)

     /*
     * 读取输出格式的引用字符
     *
     * 返回值 Rune - 输出格式的引用字符
     */ 
    public func getQuoteCharacter(): Rune

    /*
     * 设置输出格式的列值引用模式
     *
     * 参数 quoteMode - 引用模式 
     *
     * 返回值 CSVOutFormat - 返回对象本身
     */
    public func setQuoteMode(quoteMode: QuoteMode): CSVOutFormat

     /*
     * 读取输出格式的列值引用模式
     *
     * 返回值 QuoteMode - 输出格式的列值引用模式
     */ 
    public func getQuoteMode(): QuoteMode 

    /*
     * 设置输出格式的分隔符
     *
     * 参数 delimiter - 分隔符 
     *
     * 返回值 CSVOutFormat - 返回对象本身
     */
    public func setDelimiter(delimiter: String): CSVOutFormat

     /*
     * 读取输出格式的分隔符
     *
     * 返回值 String - 输出格式的分隔符
     */ 
    public func getDelimiterString(): String

    /*
     * 设置输出格式的注释符
     *
     * 参数 commentMarker - 注释符 
     *
     * 返回值 CSVOutFormat - 返回对象本身
     */
    public func setCommentMarker(commentMarker: Rune): CSVOutFormat

     /*
     * 读取输出格式的注释符
     *
     * 返回值 Rune - 输出格式的注释符
     */ 
    public func getCommentMarker(): Rune

     /*
     * 读取记录分隔标志,一般是回车换行
     *
     * 返回值 String - 记录分隔标志
     */ 
    public func getRecordSeparator(): String


    /*
     * 设置使用引用模式的列的序号数组
     *
     * 参数 quotedColsIndex - 使用引用模式的列的序号数组
     *
     * 返回值 CSVOutFormat - 返回对象本身
     */
    public func setQuotedColsIndex(quotedColsIndex: Array<Int64>): CSVOutFormat 

     /*
     * 读取使用引用模式的列的序号数组
     *
     * 返回值 Array<Int64> - 使用引用模式的列的序号数组
     */ 
    public func getQuotedColsIndex(): Array<Int64>
}

/*
  * 列值的引用模式枚举
  */
public enum QuoteMode {

    // 引用所有列
    All |
    // 引用指定的列
    CfgCols |
    // 引用包含特殊字符的列
    SpecialValue |
    //不引用,如果有特殊字符就转义
    None
}
2.1.2 示例
import std.collection.*
import csv4cj.*

main() {
    let x: Rune = '|'
    let f: CSVOutFormat  = CSVOutFormat.DEFAULT.setEscapeCharacter(x)
    println("${f.getEscapeCharacter()}")
    
    let h: Array<String> = ["hello"]
    f.setHeader(h)
    println("${f.getHeader().toString()}")

    f.setIgnoreEmptyRecord(true)
    println("${f.getIgnoreEmptyRecord()}")


    let c: Rune = '"'
    f.setQuoteCharacter(c)
    println("${f.getQuoteCharacter().toString()}")


    f.setTrim(true)
    println("${f.getTrim()}")

    let c1: Rune = '#'
    f.setCommentMarker(c1)
    println("${f.getCommentMarker().toString()}")

    f.setDelimiter("---")
    println("${f.getDelimiterString()}")

    let a: Array<Int64> = [1,2]
    f.setQuotedColsIndex(a)
    println("${f.getQuotedColsIndex().toString()}")


    let q: QuoteMode  = CfgCols 
    f.setQuoteMode(q)
    return 0
}

2.2 将解析出的csv结果转为json

CSV记录

2.2.1 主要接口
public class CSVRecord <: Iterable<String> & Serializable<CSVRecord> {
    
    /*
     * 构造函数
     *
     * 参数 values - 列值数组 
     * 参数 comment - 注释
     */
    public init(values: Array<String>, comment: Option<String>)

    /*
     * 构造函数
     *
     * 参数 values - 列值列表
     * 参数 comment - 注释
     */
    public init(values: ArrayList<String>, comment: Option<String>)
 
    /*
     * 构造函数
     *
     * 参数 values - 列值数组 
     */
    public init(values: Array<String>) 

    /*
     * 构造函数
     *
     * 参数 values - 列值列表 
     */
    public init(values: ArrayList<String>) 

    /*
     * 构造函数
     */
    public init()

    /*
     * 获取给定序号对应的列值
     *
     * 参数 index - 列序号 
     *
     * 返回值 :String- 给定序号对应的列值
     */
    public func get(index: Int64):String

    /*
     * 获取给定类标题对应的列值
     *
     * 参数 header - 列标题 
     * 参数 headerDict - 列标题和列序号组成的字典
     *
     * 返回值 :String- 给定列标题对应的列值
     */
    public func get(header: String, headerDict: HashMap<String, Int64>):String

    /*
     * 获取列值迭代器
     *
     * 返回值 Iterator<String> - 迭代器
     */
    public func iterator(): Iterator<String>

    /*
     * 获取行序号,当行序号没有设置时返回1
     *
     * 返回值 Int64 - 行序号
     */
    public func getRecordNumber():Int64

    /*
     * 获取注释
     *
     * 返回值 String - 注释
     */
    public func getComment(): Option<String>

    /*
     * 获取记录列的数量
     *
     * 返回值 :Int64 - 列的数量
     */
    public func size():Int64

    /*
     * 获取记录的列值字符串列表
     *
     * 返回值 :Array<String> - 列值字符串列表
     */
    public func getValues(): Array<String>

    /*
     * 记录序列化
     *
     * 返回值 :DataModel - 序列化后的对象
     */
    public func serialize(): DataModel

    /*
     * 对给定的对象反序列化得到CSVRecord对象,参数不是 DataModelStruct 类型抛 Exception 异常
     *
     * 参数 dm - 反序列对象 
     *
     * 返回值 CSVRecord- 反序列化得到的CSVRecord对象
     */
    static public func deserialize(dm: DataModel): CSVRecord

}
2.2.2 示例
import stdx.serialization.serialization.*
import stdx.encoding.json.*
import std.collection.*
import std.posix.*
import csv4cj.*

main() {
    testInit()
    testSerializeDeserialize()
    return 0
}

func testInit(): Unit {
    var csvRecord = CSVRecord()
    let values = ["1", "a", "cangjie", "开发"]
    csvRecord = CSVRecord(values)
    let valueList = ArrayList<String>(values)
    csvRecord = CSVRecord(valueList)
    csvRecord = CSVRecord(valueList,"")
    csvRecord.size()
    csvRecord.iterator()
}

func testSerializeDeserialize(): Unit {
    var csvRecord = CSVRecord(["1", "a", "cangjie", "开发"], "注释")
    let josnValue = csvRecord.serialize().toJson()
    let jsonString = josnValue.toJsonString()
    let newRecord = CSVRecord.deserialize(DataModel.fromJson(josnValue))
}

2.3 CSV格式的输出对象

CSV格式的输出对象

2.3.1 主要接口
public class CSVPrinter {
    
    /*
     * 主构造函数
     *
     * 参数 format - 内容输出配置 
     */
    public CSVPrinter(format: CSVOutFormat)

    /*
     * 输出记录到out对象
     *
     * 参数 csvRecord - 要输出的记录 
     * 参数 out - 输出的目的对象 
     */
    public func print(csvRecord: CSVRecord, out: Appendable): Unit

    /*
     * 输出记录到out对象,包括注释
     *
     * 参数 csvRecord - 要输出的记录 
     * 参数 out - 输出的目的对象 
     */
    public func printWithComment(csvRecord: CSVRecord, out: Appendable): Unit 

    /*
     * 输出配置的标题行到out对象
     *
     * 参数 out - 输出的目的对象 
     */
    public func printHeader(out: Appendable): Unit

     /*
     * 输出注释到out对象
     *
     * 参数 comment - 要输出的注释
     * 参数 out - 输出的目的对象 
     */
    public func printComment(comment: Option<String>, out: Appendable): Unit 

    /*
     * 输出回车换行到out对象
     *
     * 参数 out - 输出的目的对象 
     */
    public func printLine(out: Appendable): Any

    /*
     * 输出values和回车换行到out对象
     *
     * 参数 values - 要输出的列表对象 
     * 参数 out - 输出的目的对象 
     */
    public func printLine(values: Iterable<String>, out: Appendable): Unit

    /*
     * 输出values到out对象
     *
     * 参数 values - 要输出的列表对象 
     * 参数 out - 输出的目的对象 
     */
    public func print(values: Iterable<String>, out: Appendable): Unit 

}

/*
  * 定义 Appendable 接口
  */
public interface Appendable {
    /*
     * 把字符串str写入或者追加到末尾,由具体实现决定
     *
     * 参数 str - 要追加的字符串
     *
     * 返回值 Any - 返回对象本身
     */
    func append(str: String): Any

    /*
     * 把字符chr写入或者追加到末尾,由具体实现决定
     *
     * 参数 chr - 要追加的字符
     *
     * 返回值 Any - 返回对象本身
     */
    func append(chr: Rune): Any
}

/*
  *为 StringBuilder 扩展 Appendable 接口
  */
extend StringBuilder <: Appendable {}

/*
  *为 Console 扩展 Appendable 接口
  */
extend Console <: Appendable {
    /*
     * 输出字符串str到控制台
     *
     * 参数 str - 要输出的字符串
     *
     * 返回值 Console - 返回对象本身
     */
    public func append(str: String) :Console

    /*
     * 输出字符chr到控制台
     *
     * 参数 chr - 要输出的字符
     *
     * 返回值 Console - 返回对象本身
     */
    public func append(chr: Rune) :Console
}
2.3.2 示例
import stdx.serialization.serialization.*
import stdx.encoding.json.*
import std.collection.*
import std.io.*
import std.posix.*
import csv4cj.*

main() {
    println("Csv输出:")
    csvPrint()
    printHeader()
    printSpecialCols()
    PrintWithNoneMode()
    printWithComment()
    return 0
}

public func printWithComment(): Unit {
    let csvRecord = CSVRecord(["1", "taobao", "小米", "200斤"], "注释\r\n第二行")
    let format = CSVOutFormat.DEFAULT
    let csvPrint = CSVPrinter(format)
    let sb = StringBuilder()

    csvPrint.printWithComment(csvRecord, sb)
}

func printHeader() {
    let header = ["姓名", "年龄", "学号", "成绩"]
    let outFormat = CSVOutFormat.DEFAULT.setHeader(header)

    let csvPrint = CSVPrinter(outFormat)
    let output = Console()
    csvPrint.printHeader(output)
    csvPrint.printLine(output)
    csvPrint.printLine(["赵林", "15", "06", "89"], output)
    csvPrint.printLine(["李帅", "16", "15", "92"], output)
    csvPrint.print(["唐明", "15", "36", "61"], output)
}

func printSpecialCols(): Unit {
    let format = CSVOutFormat.DEFAULT.setQuoteMode(QuoteMode.CfgCols).setQuotedColsIndex([1, 2])
    let csvPrint = CSVPrinter(format)
    let sb = StringBuilder()
    csvPrint.print(["abc,", "1\\2,3", "a\tb", ","], sb)
}

func PrintWithNoneMode(): Unit {
    let format = CSVOutFormat.DEFAULT.setQuoteMode(QuoteMode.None)
    let csvPrint = CSVPrinter(format)
    let sb = StringBuilder()

    csvPrint.print(["abc", "a\r\nb\nc", "a\tb", ","], sb)

    var csvRecord = CSVRecord(["1", "a", "cangjie", "开发"], "注释")
    csvPrint.print(csvRecord, sb)
}

//按照csv格式输出
func csvPrint() {
    let csvContent = 
        ###"# Comment before header\\n
author,title,publishDate\\r
Dan Simmons|,Hyperion,"1989"
# Comment Line 1
# Comment Line 2|
# Comment Line 3|
Douglas ||Adams,The Hitchhiker's \"Guide\" to the Galaxy,1979
你好"###

    //创建字符串流
    let readerStream = StringStream(csvContent)

    let reader = CSVReader(readerStream)

    //创建格式化的解析参数
    let format: CSVParseFormat = CSVParseFormat.DEFAULT

    //创建解析器
    let csvParser = CSVParser(reader, format)

    let recordList = csvParser.getRecords()
    let outFormat = CSVOutFormat.DEFAULT.setQuoteMode(All).setDelimiter("|")
    let outFormat2 = CSVOutFormat.DEFAULT.setQuoteMode(QuoteMode.None).setDelimiter("||")
    let sbOut = StringBuilder()
    let csvPrint = CSVPrinter(outFormat)
    let csvPrint2 = CSVPrinter(outFormat2)

    var firstLine = true

    //遍历每一行解析记录
    for (csvRecord in recordList) {
        if (firstLine) {
            firstLine = false
            csvPrint.print(csvRecord, sbOut)
            csvPrint2.print(csvRecord, sbOut)
        } else {
            csvPrint.printLine(sbOut)
            csvPrint2.printLine(sbOut)
            csvPrint.print(csvRecord, sbOut)
            csvPrint2.print(csvRecord, sbOut)
        }
    }
    println(sbOut.toString())
}

示例:

示例 test.csv 文件如下:

id,name,age,remark
30020,biboo,28,middle school student
30021,张飞,45,三国演义男4号𥻗𪧘 
#这是第3行的注释,里面有转义的tab键和引用符号
30022,关\t羽,48,著名的武财神αβγ
30023,刘备,50,"胳膊比较长,善于编草鞋"

示例 test_extra.csv 文件如下:

id𪧘𪧘name𪧘𪧘age𪧘𪧘remark
30020𪧘𪧘biboo𪧘𪧘28𪧘𪧘middle school student
α这是第3行的注释,里面有转义的tab键和引用符号
30022𪧘𪧘关\t羽𪧘𪧘 48 𪧘𪧘著名的武财神 

示例代码如下:

import std.fs.*
import stdx.serialization.serialization.*
import stdx.encoding.json.*
import std.collection.*
import std.posix.*
import csv4cj.*

main() {
    let path: String = getcwd()
    if (path.isEmpty()) {
        println("请指定要解析的文件路径")
        return 1
    }

    println("自定义输出:")
    customPrintingDemo("${path}/test.csv")

    println("Json输出:")
    serialPrintingDemo("${path}/test_extra.csv")

    println("Csv输出:")
    csvPrint()
    return 0
}

//自定义输出格式
func customPrintingDemo(fileName: String) {
    //创建文件流
    let fileStream = File(fileName, OpenMode.Read)

    if (fileStream.canRead()) {
        //创建字符读取的解析流
        let stream = UTF8ReaderStream(fileStream)
        let reader = CSVReader(stream)

        //创建格式化的解析参数
        let format: CSVParseFormat = CSVParseFormat.DEFAULT.setSkipHeaderRecord(true).setFirstLineAsHeader(true)

        //创建解析器
        let csvParser = CSVParser(reader, format)

        //遍历每一行解析记录
        for (csvRecord in csvParser) {
            let rowNo = csvRecord.getRecordNumber()

            //使用表头标题获取对应列的值
            let id = csvRecord.get("id",csvParser.getHeaderDict()) ?? ""
            let name = csvRecord.get("name",csvParser.getHeaderDict()) ?? ""

            //使用列序号获取对应列的值
            let age = csvRecord.get(2)
            let remark = csvRecord.get(3)
            if (let Some(comment) = csvRecord.getComment()) {
                println(
                    "RowNo:${rowNo}|Id:${id}|name:${name}|age:${age}|remark:${remark}|comment:${comment}")
            } else {
                println("RowNo:${rowNo}|Id:${id}|name:${name}|age:${age}|remark:${remark}")
            }
        }
        fileStream.close()
    }
}

//Json格式输出
func serialPrintingDemo(fileName: String) {
    //创建文件流
    let fileStream = File(fileName, OpenMode.Read)

    if (fileStream.canRead()) {
        //创建字符读取的解析流
        let stream = UTF8ReaderStream(fileStream)

        let reader = CSVReader(stream)

        //创建格式化的解析参数
        let format: CSVParseFormat = CSVParseFormat.DEFAULT.setCommentMarker('α').setDelimiter("𪧘𪧘")

        //创建解析器
        let csvParser = CSVParser(reader, format)

        let recordList = csvParser.getRecords()

        //遍历每一行解析记录
        for (csvRecord in recordList) {
            println(csvRecord.serialize().toJson())
            CSVRecord.deserialize(csvRecord.serialize())
        }
        fileStream.close()
    }
}

//按照csv格式输出
func csvPrint() {
    let csvContent = 
        ###"# Comment before header
author,title,publishDate
Dan Simmons,Hyperion,"1989"
# Comment Line 1
# Comment Line 2
# Comment Line 3
Douglas Adams,The Hitchhiker's \"Guide\" to the Galaxy,1979
Douglas John,The Hitchhiker's \"Guide\" to the Mars,1979"###

    //创建字符串流
    let readerStream = StringStream(csvContent)

    let reader = CSVReader(readerStream)

    //创建格式化的解析参数
    let format: CSVParseFormat = CSVParseFormat.DEFAULT

    //创建解析器
    let csvParser = CSVParser(reader, format)

    let recordList = csvParser.getRecords()
    let outFormat = CSVOutFormat.DEFAULT
    let sbOut = StringBuilder()
    let csvPrint = CSVPrinter(outFormat)

    var firstLine = true

    //遍历每一行解析记录
    for (csvRecord in recordList) {
        if (firstLine) {
            firstLine = false
            csvPrint.print(csvRecord, sbOut)
        } else {
            csvPrint.printLine(sbOut)
            csvPrint.print(csvRecord, sbOut)
        }
    }
    println(sbOut.toString())
}