如何使用Parslet将JSON中的嵌套数组字符串(如字符串)转换为结构化对象

时间:2019-06-25 08:09:39

标签: ruby grammar parslet

将解析的类似于JSON的字符串(包含嵌套数组)转换为结构化对象时遇到问题。我正在使用parslet。

我创建了解析器和转换器,如下所示。但是我无法处理嵌套数组的情况。

<?php
require_once '../connection/connection.php';
header("Access-Control-Allow-Origin: *");
require ('FPDF/fpdf.php');

//A4 width: 219mm
//default margin: 10mm each side
//writable horizontal: 219-(10*2)=189mm

$sql = "SELECT * FROM add_booking INNER JOIN customers ON add_booking.customer_ID = customers.ID ORDER BY Booking_ID DESC";
$count = 1;

//Values I want to show in PDF
//Values I want to show in PDF
foreach ($conn->query($sql) as $row) {
    $updatecount = $count++;
    $first_name = $row[first_name];
    $last_name = $row[last_name];
    $email = $row[email];
}
//Values I want to show in PDF
//Values I want to show in PDF

if (isset($_POST['assign-driver'])) {

    if (empty($_POST['assignDriver'])) {
        header("location:booking.php?failDriverAssign= Please select the driver!");
    } 

    else { 
        $booking_id = $_POST['booking_id'];
        $assignDriver = $_POST['assignDriver'];

        $query = "UPDATE add_booking SET assigned_driver='$assignDriver' WHERE Booking_ID=$booking_id";
        $result = mysqli_query($conn, $query);

        if (isset($result)) {

            // PDF CODE

                $pdf = new FPDF('P', 'mm', 'A4');
                $pdf->AddPage();

                //set font to arial, bold, 14pt
                $pdf->SetFont('Arial', 'B', 14);

                //Cell (width, height, text, border, end line, {align})

                $pdf->Cell (130, 5, '', 1, 0);
                $pdf->Cell (70, 5, '', 1, 0);


                $pdf->Output();

            // PDF CODE

            header("location:booking.php?passDriverAssign= Driver has been assigned. Booking Confirmation has been sent to Driver and Customer!");
        } 

        else {
            header("location:booking.php?failDriverAssign= Something went wrong. Check back in few minutes!");
        }
    }
}

问题字符串为require 'parslet' class JSONLikeDataParser < Parslet::Parser rule(:l_map_paren) {str('{')} rule(:r_map_paren) {str('}')} rule(:l_list_paren) {str('[')} rule(:r_list_paren) {str(']')} rule(:map_entry_delimiter) {str(':')} rule(:val_delimiter) {str(',')} rule(:quote) {str('"')} rule(:simple_val) {match('[^",:\.\{\}\[\]]').repeat(1)} rule(:quoted_val) {quote >> (quote.absnt? >> any).repeat(0) >> quote} rule(:map) {l_map_paren >> map_entries.maybe.as(:map) >> r_map_paren} rule(:map_entries) {map_entry >> (val_delimiter >> map_entry).repeat} rule(:map_entry) {map_key >> map_entry_delimiter >> object} rule(:map_key) {(match('[A-Za-z_]').repeat(1) >> match('[A-Za-z0-9_]').repeat).as(:key)} rule(:list) {l_list_paren >> list_values.maybe.as(:list) >> r_list_paren} rule(:list_values) {object >> (val_delimiter >> object).repeat} rule(:object) {map | (simple_val | quoted_val).as(:value) | list } root(:object) end #TODO doesn't handle properly nested array: [[[1,2],[3]]] class JSONLikeDataTransform < Parslet::Transform rule(map: subtree(:s)) do ret = {} if (s.is_a?(Hash)) ret[s[:key]] = s[:value] else s.each do |h| ret.merge!(h) end end OpenStruct.new(ret) end rule(key: simple(:k), value: simple(:v)) {{k.str => v.str}} rule(key: simple(:k), list: simple(:v)) {{k.str => [v]}} rule(key: simple(:k), list: sequence(:v)) {{k.str => v}} rule(map: simple(:s)) {s ? OpenStruct.new(s) : OpenStruct.new} rule(list: subtree(:s)) {[s]} rule(list: sequence(:s)) {s} rule(list: simple(:s)) {s ? [s] : []} rule(value: subtree(:s)) {s} rule(value: sequence(:s)) {s} rule(value: simple(:s)) {s.str} end puts JSONLikeDataTransform.new.apply(JSONLikeDataParser.new.parse("[[[1],[2,3]]]")).inspect 。我希望收到正确的嵌套结构。但是我得到的是"[[[1],[2,3]]]"一个括号太多了。

1 个答案:

答案 0 :(得分:1)

尤其感谢大家分享@NigelThorne,他在其他主题中的回答使我得以实际解决此问题。我引入了一些其他类,例如Map / Value / Arr,因此我能够识别出特定数组是由Transform框架创建的还是列表匹配的结果。

下面是一个工作代码和一些测试,以供将来参考。

require 'parslet'
require 'parslet/convenience'
require 'ostruct'


module JSONLikeDataModule
  module Parsing

    class GobbleUp < Parslet::Atoms::Base
      def initialize absent, min_chars = 0
        @absent = absent
        @min_chars = min_chars
      end

      def try(source, context, consume_all)
        excluding_length = source.chars_until(@absent)

        if excluding_length >= @min_chars
          return succ(source.consume(excluding_length))
        else
          return context.err(self, source, "No such string in input: #{@absent.inspect}.")
        end
      end

      def to_s_inner(prec)
        "until('#{@absent}')"
      end
    end

    class JSONLikeDataParser < Parslet::Parser
      rule(:l_map_paren) {str('{')}
      rule(:r_map_paren) {str('}')}
      rule(:l_list_paren) {str('[')}
      rule(:r_list_paren) {str(']')}
      rule(:map_entry_delimiter) {str(':')}
      rule(:val_delimiter) {str(',')}
      rule(:quote) {str('"')}

      rule(:simple_val) {match('[^",:\.\{\}\[\]]').repeat(1)}
      rule(:quoted_val) {quote >> GobbleUp.new('"').as(:value) >> quote}


      rule(:map) {l_map_paren >> map_entries.maybe.as(:map) >> r_map_paren}
      rule(:map_entries) {map_entry >> (val_delimiter >> map_entry).repeat}
      rule(:map_entry) {map_key >> map_entry_delimiter >> object}
      rule(:map_key) {(match('[A-Za-z_]').repeat(1) >> match('[A-Za-z0-9_]').repeat).as(:key)}

      rule(:list) {l_list_paren >> list_values.maybe.as(:list) >> r_list_paren}
      rule(:list_values) {object >> (val_delimiter >> object).repeat}

      rule(:object) {map.as(:value) | simple_val.as(:value) | quoted_val | list.as(:value)}

      root(:object)
    end

    class JSONLikeDataTransform < Parslet::Transform
      rule(key: simple(:key), value: simple(:value)) {{builder.value(key) => builder.value(value)}}

      rule(map: subtree(:s)) do
        ret = {}
        next builder.map(ret) unless s

        to_transform = s
        if to_transform.is_a?(Hash)
          to_transform = [to_transform]
        end

        to_transform.each do |h|
          ret.merge!(h)
        end
        builder.map(ret)
      end

      rule(list: simple(:list)) {builder.list(list)}
      rule(list: sequence(:list)) {builder.list(list)}
      rule(list: subtree(:list)) {builder.list(list)}

      rule(value: simple(:value)) {builder.value(value)}
      rule(value: sequence(:value)) {value.map {|val| builder.value(val)}}
      rule(value: subtree(:value)) {builder.value(value)}

    end



    class Builder
      def map(h)
        Map.new(h)
      end

      def list(l)
        Arr.new(l)
      end

      def value(v)
        Value.new(v)
      end

      class Arr
        def initialize(val)
          @val = val
        end

        def val
          return [] unless @val
          return @val.map(&:val) if @val.is_a?(Array)
          return [@val.val]
        end
      end

      class Map
        def initialize(val)
          @val = val
        end

        def val
          return OpenStruct.new unless @val
          @val.inject(OpenStruct.new) do |ostruct, (k,v)|
            ostruct[k.val] = v.val
            ostruct
          end
        end
      end

      class Value
        def initialize(val)
          @val = val
        end

        def val
          @val.respond_to?(:str) ? @val.str : @val.val
        end
      end
    end
  end
end

module JSONLikeDataModule
  class JSONLikeDataFactory
    @@flag_data_parser ||= Parsing::JSONLikeDataParser.new
    @@flag_data_transform ||= Parsing::JSONLikeDataTransform.new

    class << self
      private :new

      def create(flag_data_str)
        parsed_tree = @@flag_data_parser.parse_with_debug(flag_data_str)
        ret = @@flag_data_transform.apply(parsed_tree, :builder => Parsing::Builder.new)
        ret.val
      end
    end
  end
end

测试

require 'minitest/autorun'
class JSONLikeFactoryTest < Minitest::Test
  include JSONLikeDataModule
  describe "JSONLikeDataFactory" do

    subject do
      JSONLikeDataFactory
    end

    it "should create string val" do
      subject.create('_S').must_equal "_S"
    end

    it "should create empty array" do
      subject.create('[]').must_equal []
    end

    it "should create empty nested array" do
      subject.create('[[[]]]').must_equal [[[]]]
    end

    it "should create not empty nested array" do
      subject.create('[[[1],[2,3]]]').must_equal [[['1'],['2','3']]]
    end

    it "should create empty OpenStruct" do
      subject.create('{}').must_equal OpenStruct.new
    end

    it "should create filled 1level OpenStruct" do
      subject.create('{key1:val,key2:"val"}').must_equal OpenStruct.new(key1: "val", key2: "val")
    end

    it "should create filled 2levels OpenStruct" do
      subject.create('{key1:val,key2:"val",key3:{},key4:{key1:val},key5:[1,2],key6:[[1,2,3],{},1,{key1:"[]{}:,."}]}').must_equal o(key1: "val", key2: "val", key3: o, key4: o(key1: "val"), key5: %w(1 2), key6: [%w(1 2 3), o, '1', o(key1: '[]{}:,.')])
    end

    def o(h= {})
      OpenStruct.new(h)
    end
  end
end