如何在parlet中定义固定宽度约束



我正在寻找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

当然,这是一个相当黑的解决方案。在固定宽度约束中,行号和错误消息是不好的。

最新更新