我正在研究parslet来编写很多数据导入代码。总体而言,图书馆看起来不错,但我正在努力做一件事。我们的输入文件很多都是固定的宽度,格式之间的宽度不同,即使实际的字段没有。例如,我们可能会得到一个9字符货币的文件,另一个有11个字符(或其他)的文件。有谁知道如何在parslet原子上定义固定宽度约束?
理想情况下,我希望能够定义一个理解货币的原子(带有可选的美元符号,千位分隔符等等)然后我可以在运行中创建一个基于的新原子除了它正好解析N个字符之外,它是完全等价的旧的。
这样的组合器是否存在于parslet中?如果没有,是否有可能/很难自己写一个?
答案 0 :(得分:1)
解析器类中的方法基本上是用于parslet原子的生成器。这些方法最简单的形式是'规则',每次调用它们时返回相同原子的方法。创建自己的发电机同样容易,而不是那么简单的野兽。请查看http://kschiess.github.com/parslet/tricks.html以了解此技巧的说明(匹配字符串不区分大小写)。
在我看来,您的货币解析器是一个只有少量参数的解析器,您可以创建一个方法(def ... end),返回根据您的喜好定制的货币解析器。甚至可以使用initialize和constructor参数? (即:MoneyParser.new(4,5))
如需更多帮助,请将您的问题发送到邮件列表。如果用代码说明这些问题通常更容易回答。
答案 1 :(得分:1)
也许我的部分解决方案有助于澄清我在问题中的含义。
假设你有一个非常简单的解析器:
class MyParser < Parslet::Parser
rule(:dollars) {
match('[0-9]').repeat(1).as(:dollars)
}
rule(:comma_separated_dollars) {
match('[0-9]').repeat(1, 3).as(:dollars) >> ( match(',') >> match('[0-9]').repeat(3, 3).as(:dollars) ).repeat(1)
}
rule(:cents) {
match('[0-9]').repeat(2, 2).as(:cents)
}
rule(:currency) {
(str('$') >> (comma_separated_dollars | dollars) >> str('.') >> cents).as(:currency)
# order is important in (comma_separated_dollars | dollars)
}
end
现在,如果我们要解析固定宽度的货币字符串;这不是最简单的事情。当然,你可以弄清楚如何用最终宽度来表达重复表达式,但它实际上变得非常棘手,特别是在逗号分隔的情况下。另外,在我的用例中,货币实际上只是一个例子。我希望能够轻松地为地址,邮政编码等提供固定宽度的定义....
这似乎是PEG应该可以处理的事情。我设法编写了一个原型版本,使用Lookahead作为模板:
class FixedWidth < Parslet::Atoms::Base
attr_reader :bound_parslet
attr_reader :width
def initialize(width, bound_parslet) # :nodoc:
super()
@width = width
@bound_parslet = bound_parslet
@error_msgs = {
:premature => "Premature end of input (expected #{width} characters)",
:failed => "Failed fixed width",
}
end
def try(source, context) # :nodoc:
pos = source.pos
teststring = source.read(width).to_s
if (not teststring) || teststring.size != width
return error(source, @error_msgs[:premature]) #if not teststring && teststring.size == width
end
fakesource = Parslet::Source.new(teststring)
value = bound_parslet.apply(fakesource, context)
return value if not value.error?
source.pos = pos
return error(source, @error_msgs[:failed])
end
def to_s_inner(prec) # :nodoc:
"FIXED-WIDTH(#{width}, #{bound_parslet.to_s(prec)})"
end
def error_tree # :nodoc:
Parslet::ErrorTree.new(self, bound_parslet.error_tree)
end
end
# now we can easily define a fixed-width currency rule:
class SHPParser
rule(:currency15) {
FixedWidth.new(15, currency >> str(' ').repeat)
}
end
当然,这是一个非常黑客的解决方案。除此之外,行数和错误消息在固定宽度约束内不好。我希望看到这个想法以更好的方式实施。
答案 2 :(得分:1)
这样的事情......
class MyParser < Parslet::Parser
def initialize(widths)
@widths = widths
super
end
rule(:currency) {...}
rule(:fixed_c) {currency.fixed(@widths[:currency])}
rule(:fixed_str) {str("bob").fixed(4)}
end
puts MyParser.new.fixed_str.parse("bob").inspect
这将失败:
"Expected 'bob' to be 4 long at line 1 char 1"
这是你如何做到的:
require 'parslet'
class Parslet::Atoms::FixedLength < Parslet::Atoms::Base
attr_reader :len, :parslet
def initialize(parslet, len, tag=:length)
super()
raise ArgumentError,
"Asking for zero length of a parslet. (#{parslet.inspect} length #{len})" \
if len == 0
@parslet = parslet
@len = len
@tag = tag
@error_msgs = {
:lenrep => "Expected #{parslet.inspect} to be #{len} long",
:unconsumed => "Extra input after last repetition"
}
end
def try(source, context, consume_all)
start_pos = source.pos
success, value = parslet.apply(source, context, false)
return succ(value) if success && value.str.length == @len
context.err_at(
self,
source,
@error_msgs[:lenrep],
start_pos,
[value])
end
precedence REPETITION
def to_s_inner(prec)
parslet.to_s(prec) + "{len:#{@len}}"
end
end
module Parslet::Atoms::DSL
def fixed(len)
Parslet::Atoms::FixedLength.new(self, len)
end
end