2011-04-04 54 views
3

我正在研究parslet來編寫大量的數據導入代碼。總的來說,圖書館看起來不錯,但我正在努力處理一件事。我們的輸入文件的大部分都是固定寬度,並且格式之間的寬度不同,即使實際字段沒有。例如,我們可能會得到一個具有9個字符貨幣的文件,另一個具有11個字符(或其他)的文件。有誰知道如何在一個parslet原子上定義一個固定的寬度約束?如何在parslet中定義一個固定寬度的約束條件

理想情況下,我希望能夠定義一個理解貨幣的原子(帶有可選的美元符號,千分隔符等)。然後,我可以在運行時創建一個新的原子在舊的完全相同的,除了它分析正好N個字符。

這樣的combinator是否存在於parslet中?如果不是,自己寫一個會有可能/很難嗎?

+1

+1不與召喚邪神一個正則表達式。 – 2011-04-13 22:38:42

回答

1

解析器類中的方法基本上是用於parslet原子的生成器。這些方法最簡單的形式是'規則',每次調用時都會返回相同的原子。創建自己的發電機並非如此簡單的野獸一樣容易。請看http://kschiess.github.com/parslet/tricks.html瞭解這個技巧的例子(匹配字符串不區分大小寫)。

在我看來,你的貨幣解析器是一個只有少數幾個參數的解析器,你可能會創建一個方法(def ... end),返回適合你喜歡的貨幣解析器。也許甚至使用初始化和構造函數參數? (即:MoneyParser.new(4,5))

如需更多幫助,請將您的問題發至郵件列表。如果您使用代碼進行說明,這些問題通常更容易回答。

+0

感謝您的迴應!我知道你可以爲特定任務構建參數化規則。但是,這不是我期望的解決方案的品牌。我希望能定義一個沒有固定寬度概念的Atom,然後在不修改原始原子的情況下稍後添加約束。 – Jonathan 2011-04-13 15:13:28

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 

當然,這是一個相當砍死解決方案。除此之外,行號和錯誤消息在固定寬度約束內不好。我很想看到這個想法以更好的方式實現。

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 
+0

'precedence'和'to_s_inner'可能需要修復。 – 2014-02-12 05:49:49