使用 parboiled2 解析多行而不是字符串
Using parboiled2 to parse multiple lines instead of a String
我想使用 parboiled2 来解析多个 CSV 行而不是单个 CSV 字符串。结果将类似于:
val parser = new CSVRecordParser(fieldSeparator)
io.Source.fromFile("my-file").getLines().map(line => parser.record.run(line))
其中 CSVRecordParser 是我的 CSV 记录解析器。我遇到的问题是,对于我所尝试的,我不能这样做,因为半熟解析器需要在构造函数中输入,而不是在 运行 方法中输入。因此,我可以为每一行创建一个新的解析器,这不太好,或者找到一种方法将输入传递给解析器,用于我拥有的每个输入。我试图通过将输入设置为变量并将解析器包装在另一个对象中来破解解析器
object CSVRecordParser {
private object CSVRecordParserWrapper extends Parser with StringBuilding {
val textBase = CharPredicate.Printable -- '"'
val qTextData = textBase ++ "\r\n"
var input: ParserInput = _
var fieldDelimiter: Char = _
def record = rule { zeroOrMore(field).separatedBy(fieldDelimiter) ~> (Seq[String] _) }
def field = rule { quotedField | unquotedField }
def quotedField = rule {
'"' ~ clearSB() ~ zeroOrMore((qTextData | '"' ~ '"') ~ appendSB()) ~ '"' ~ ows ~ push(sb.toString)
}
def unquotedField = rule { capture(zeroOrMore(textData)) }
def textData = textBase -- fieldDelimiter
def ows = rule { zeroOrMore(' ') }
}
def parse(input: ParserInput, fieldDelimiter: Char): Result[Seq[String]] = {
CSVRecordParserWrapper.input = input
CSVRecordParserWrapper.fieldDelimiter = fieldDelimiter
wrapTry(CSVRecordParserWrapper.record.run())
}
}
然后在我想解析一行时调用 CSVRecordParser.parse(input, separator)
。除了这很可怕之外,它不起作用,而且我经常遇到与以前使用解析器相关的奇怪错误。我知道这不是我应该使用 parboiled2 编写解析器的方式,我想知道什么是实现我想用这个库做的事情的最佳方式。
我已经在一个需要高速和低资源的项目中为超过 100 万条记录的 CSV 文件完成了此操作,我发现为每一行实例化一个新的解析器效果很好。
在我注意到 parboiled2 自述文件提到解析器的重量非常轻后,我尝试了这种方法。
我什至不需要增加默认值的 JVM 内存或堆限制。每行的解析器实例化效果很好。
为什么不向解析器添加记录结束规则。
def EOR = rule { "\r\n" | "\n" }
def record = rule { zeroOrMore(field).separatedBy(fieldDelimiter) ~ EOR ~> (Seq[String] _) }
然后你想传多少行就传多少行。
我想使用 parboiled2 来解析多个 CSV 行而不是单个 CSV 字符串。结果将类似于:
val parser = new CSVRecordParser(fieldSeparator)
io.Source.fromFile("my-file").getLines().map(line => parser.record.run(line))
其中 CSVRecordParser 是我的 CSV 记录解析器。我遇到的问题是,对于我所尝试的,我不能这样做,因为半熟解析器需要在构造函数中输入,而不是在 运行 方法中输入。因此,我可以为每一行创建一个新的解析器,这不太好,或者找到一种方法将输入传递给解析器,用于我拥有的每个输入。我试图通过将输入设置为变量并将解析器包装在另一个对象中来破解解析器
object CSVRecordParser {
private object CSVRecordParserWrapper extends Parser with StringBuilding {
val textBase = CharPredicate.Printable -- '"'
val qTextData = textBase ++ "\r\n"
var input: ParserInput = _
var fieldDelimiter: Char = _
def record = rule { zeroOrMore(field).separatedBy(fieldDelimiter) ~> (Seq[String] _) }
def field = rule { quotedField | unquotedField }
def quotedField = rule {
'"' ~ clearSB() ~ zeroOrMore((qTextData | '"' ~ '"') ~ appendSB()) ~ '"' ~ ows ~ push(sb.toString)
}
def unquotedField = rule { capture(zeroOrMore(textData)) }
def textData = textBase -- fieldDelimiter
def ows = rule { zeroOrMore(' ') }
}
def parse(input: ParserInput, fieldDelimiter: Char): Result[Seq[String]] = {
CSVRecordParserWrapper.input = input
CSVRecordParserWrapper.fieldDelimiter = fieldDelimiter
wrapTry(CSVRecordParserWrapper.record.run())
}
}
然后在我想解析一行时调用 CSVRecordParser.parse(input, separator)
。除了这很可怕之外,它不起作用,而且我经常遇到与以前使用解析器相关的奇怪错误。我知道这不是我应该使用 parboiled2 编写解析器的方式,我想知道什么是实现我想用这个库做的事情的最佳方式。
我已经在一个需要高速和低资源的项目中为超过 100 万条记录的 CSV 文件完成了此操作,我发现为每一行实例化一个新的解析器效果很好。
在我注意到 parboiled2 自述文件提到解析器的重量非常轻后,我尝试了这种方法。
我什至不需要增加默认值的 JVM 内存或堆限制。每行的解析器实例化效果很好。
为什么不向解析器添加记录结束规则。
def EOR = rule { "\r\n" | "\n" }
def record = rule { zeroOrMore(field).separatedBy(fieldDelimiter) ~ EOR ~> (Seq[String] _) }
然后你想传多少行就传多少行。