我正在寻找parslet编写大量的数据导入代码。总的来说,图书馆看起来不错,但我有一件事很纠结。我们的许多输入文件都是固定宽度的,不同格式的宽度不同,即使实际字段没有固定宽度。例如,我们可能得到一个具有9个字符的货币的文件,而另一个具有11个字符(或其他)的文件。有谁知道如何定义一个固定的宽度约束在parslet原子?
理想情况下,我希望能够定义一个理解货币的原子(具有可选的美元符号,千位分隔符等…)然后我就可以在运行中,基于旧的原子创建一个完全相同的新原子,除了它恰好解析N个字符。
在parslet中存在这样的组合子吗?如果没有,我自己写一篇有可能吗?
这样怎么样?
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
解析器类中的方法基本上是解析器原子的生成器。这些方法最简单的形式是"规则",即每次调用时只返回相同原子的方法。创建自己的生成器并不那么简单,这同样容易。请查看http://kschiess.github.com/parslet/tricks.html这个技巧的说明(匹配字符串不区分大小写)。
在我看来,你的货币解析器是一个只有几个参数的解析器,你可能会创建一个方法(def…结束),返回根据您的喜好定制的货币解析器。甚至可能使用initialize和constructor参数?(即:MoneyParser.new(4、5)
如需更多帮助,请将您的问题发送到邮件列表。如果用代码来说明,这样的问题通常更容易回答。也许我的部分解决方案将有助于澄清我在问题中的意思。
假设你有一个很重要的解析器:
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
当然,这是一个相当黑的解决方案。在固定宽度约束中,行号和错误消息是不好的。