Как использовать Parslet со строками, а не ломтиками Parslet

Я начал использовать Parslet для анализа некоторых пользовательских данных. В примерах результирующие проанализированные данные выглядят примерно так:

{ :custom_string => "data"@6 }

И я создал Transform что-то вроде

rule(:custom_string => simple(:x)) { x.to_s }

Но это не соответствует, предположительно, потому что я передаю "данные"@6 вместо просто "данных", которые не являются простой строкой. Все примеры для Transform имеют хэши со строками, а не с Parslet::Slices, который выводит парсер. Может быть, мне не хватает шага, но я ничего не вижу в документах.

РЕДАКТИРОВАТЬ: больше образца кода (сокращенная версия, но все еще должна быть пояснительной)

original_text = 'MSGSTART/DATA1/DATA2/0503/MAR'

require "parslet"
include Parslet

module ParseExample
  class Parser < Parslet::Parser
    rule(:fs)         { str("/") }
    rule(:newline)    { str("\n") | str("\r\n") }

    rule(:msgstart) { str("MSGSTART") }
    rule(:data1) { match("\\w").repeat(1).as(:data1) }
    rule(:data2) { match("\\w").repeat(1).as(:data2) }
    rule(:serial_number) { match("\\w").repeat(1).as(:serial_number) }
    rule(:month) { match("\\w").repeat(1).as(:month) }

    rule(:first_line) { msgstart >> fs >> data1 >> fs >> data2 >> fs >> serial_number >> fs >> month >> newline }

    rule(:document) { first_line >> newline.maybe }

    root(:document)
  end
end

module ParseExample
  class Transformer < Parslet::Transform

    rule(:data1 => simple(:x)) { x.to_s }
    rule(:data2 => simple(:x)) { x.to_s }
    rule(:serial_number => simple(:x)) { x.to_s }
    rule(:month => simple(:x)) { x.to_s }
  end
end

# Run by calling...
p = ParseExample::Parser.new
parse_result = p.parse(original_text)

# => {:data1=>"data1"@6, :data2=>"data2"@12, :serial_number=>"0503"@18, :month=>"MAR"@23}

t = ParseExample::Transformer.new
transformed = t.apply(parser_result)

# Actual result => {:data1=>"data1"@6, :data2=>"data2"@12, :serial_number=>"0503"@18, :month=>"MAR"@23}

# Expected result => {:data1=>"data1", :data2=>"data2", :serial_number=>"0503", :month=>"MAR"}

1 ответ

Решение

Вы не можете заменить отдельные пары ключ / значение. Вы должны заменить весь хэш сразу.

Я влюбился в это в первый раз, когда я написал трансформеры тоже. Ключ в том, что правила преобразования соответствуют целому узлу и заменяют его... в его целостности. Как только узел был найден, он больше не посещается.

Если вы использовали хеш и сопоставляли только одну пару ключ / значение, заменяя ее значением... вы просто потеряли все другие пары ключ / значение в том же хеше.

Однако... Есть способ!

Если вы хотите предварительно обработать все узлы в хэше, прежде чем сопоставлять весь хеш, значения хэша должны быть самими хешами. Тогда вы можете сопоставить их и преобразовать их в строки. Обычно это можно сделать, просто добавив еще одно "как" в свой парсер.

Например:

original_text = 'MSGSTART/DATA1/DATA2/0503/MAR'

require "parslet"
include Parslet

module ParseExample
  class Parser < Parslet::Parser
    rule(:fs)         { str("/") }
    rule(:newline)    { str("\n") | str("\r\n") }

    rule(:msgstart) { str("MSGSTART") }

    rule(:string) {match("\\w").repeat(1).as(:string)} # Notice the as!

    rule(:data1) { string.as(:data1) }
    rule(:data2) { string.as(:data2) }
    rule(:serial_number) { string.as(:serial_number) }
    rule(:month) { string.as(:month) }

    rule(:first_line) { 
        msgstart >> fs >> 
        data1 >> fs >> 
        data2 >> fs >> 
        serial_number >> fs >> 
        month >> newline.maybe 
    }

    rule(:document) { first_line >> newline.maybe }

    root(:document)
  end
end

# Run by calling...
p = ParseExample::Parser.new
parser_result = p.parse(original_text)

puts parser_result.inspect
# => {:data1=>{:string=>"DATA1"@9}, 
      :data2=>{:string=>"DATA2"@15}, 
      :serial_number=>{:string=>"0503"@21}, 
      :month=>{:string=>"MAR"@26}}

# See how the values in the hash are now all hashes themselves.

module ParseExample
  class Transformer < Parslet::Transform
    rule(:string => simple(:x)) { x.to_s }
  end
end

# We just need to match the "{:string => x}" hashes now...and replace them with strings

t = ParseExample::Transformer.new
transformed = t.apply(parser_result)

puts transformed.inspect
# => {:data1=>"DATA1", :data2=>"DATA2", :serial_number=>"0503", :month=>"MAR"}

# Tada!!!

Если вы хотели обработать всю строку, сделайте из нее объект.. скажем..

class Entry 
   def initialize(data1:, data2:, serial_number:,month:)
      @data1 = data1
      @data2 = data2
      @serial_number = serial_number
      @month = month
   end
end

module ParseExample
  class Transformer < Parslet::Transform
    rule(:string => simple(:x)) { x.to_s }

    # match the whole hash
    rule(:data1 => simple(:d1),
         :data2 => simple(:d2),
         :serial_number => simple(:s),
         :month => simple(:m)) { 
            Entry.new(data1: d1,data2: d2,serial_number: s,month: m)} 
  end
end

t = ParseExample::Transformer.new
transformed = t.apply(parser_result)

puts transformed.inspect
# => #<Entry:0x007fd5a3d26bf0 @data1="DATA1", @data2="DATA2", @serial_number="0503", @month="MAR">
Другие вопросы по тегам