From 66d1a45311428c88ec8fad8ed218ac4f098717e4 Mon Sep 17 00:00:00 2001 From: mbklein Date: Wed, 3 Mar 2010 21:22:53 +0000 Subject: [PATCH] Fixed round-tripping of JSON<->EDI; slightly changed JSON format again git-svn-id: svn://svn.open-ils.org/ILS-Contrib/acq_edi/trunk@812 6d9bc8c9-1ec2-4278-b937-99fde70a366f --- lib/edi/edi2json.rb | 65 ++++++++++++++++++++++++++------------ lib/edi/mapper.rb | 81 ++++++++++++++++++++++++++++++++++++------------ test/map_spec.rb | 8 +++-- test/openils_map_spec.rb | 5 +-- 4 files changed, 115 insertions(+), 44 deletions(-) diff --git a/lib/edi/edi2json.rb b/lib/edi/edi2json.rb index 5cb8ad8aa..c660ab5c8 100644 --- a/lib/edi/edi2json.rb +++ b/lib/edi/edi2json.rb @@ -9,22 +9,39 @@ class Collection result = {} self.each { |child| + if self[child.name].length > 1 and result[child.name].nil? + result[child.name] = [] + end if child.is_a?(Collection) + # Data elements first hash = child.to_hash - result[child.name] = hash unless hash.empty? - unless self.children.empty? - segments = [] - self.children.each { |segment| - segments << [segment.name, segment.to_hash] - } - result[self.sg_name] = segments + unless hash.empty? + if result[child.name].is_a?(Array) + result[child.name] << hash + else + result[child.name] = hash + end end else unless child.value.nil? - result[child.name] = child.value + if result[child.name].is_a?(Array) + result[child.name] << child.value + else + result[child.name] = child.value + end end end } + + # Segment groups last + if self.respond_to?(:children) and (self.children.empty? == false) + segments = [] + self.children.each { |segment| + segments << [segment.name, segment.to_hash] + } + result[self.sg_name] = segments + end + result end @@ -40,14 +57,18 @@ class Interchange messages = [] self.each { |message| - messages << [message.name, message.to_hash] + messages << {message.name => message.to_hash} } { - 'UNA' => self.una.to_s, - 'header' => [self.header.name, self.header.to_hash], - 'body' => messages, - 'trailer' => [self.trailer.name, self.trailer.to_hash] + 'UNA' => self.una.to_hash, + 'sender' => self.header.cS002.d0004, + 'sender_qual' => self.header.cS002.d0007, + 'recipient' => self.header.cS003.d0010, + 'recipient_qual' => self.header.cS003.d0007, + 'header' => [self.header.name, self.header.to_hash], + 'body' => messages, + 'trailer' => [self.trailer.name, self.trailer.to_hash] } end @@ -57,20 +78,26 @@ class Message def to_hash segments = [] - + segments << [self.header.name, self.header.to_hash] self.find_all { |segment| segment.level < 2 }.each { |segment| segments << [segment.name, segment.to_hash] } segments << [self.trailer.name, self.trailer.to_hash] - { - 'header' => [self.header.name, self.header.to_hash], - 'body' => segments, - 'trailer' => [self.trailer.name, self.trailer.to_hash] - } + segments end end +class E::UNA + def to_hash + result = {} + [:ce_sep,:de_sep,:decimal_sign,:esc_char,:rep_sep,:seg_term].each { |field| + result[field.to_s] = self.send(field).chr + } + result + end +end + end \ No newline at end of file diff --git a/lib/edi/mapper.rb b/lib/edi/mapper.rb index bb2500377..a432bdb62 100644 --- a/lib/edi/mapper.rb +++ b/lib/edi/mapper.rb @@ -22,14 +22,14 @@ module EDI::E class Mapper extend Forwardable - attr :message + attr :message, :ic attr_accessor :defaults - def_delegators :@ic, :charset, :empty?, :groups_created, :header, - :illegal_charset_pattern, :inspect, :is_iedi?, :messages_created, - :output_mode, :output_mode=, :show_una, :show_una=, :syntax, :to_s, - :to_xml, :to_xml_header, :to_xml_trailer, :trailer, :una, :validate, - :version - +# def_delegators :@ic, :charset, :empty?, :groups_created, :header, +# :illegal_charset_pattern, :inspect, :is_iedi?, :messages_created, +# :output_mode, :output_mode=, :show_una, :show_una=, :syntax, :to_s, +# :to_xml, :to_xml_header, :to_xml_trailer, :trailer, :una, :validate, +# :version + class << self def defaults @defaults || {} @@ -106,19 +106,46 @@ module EDI::E struct['msg_opts'].each_pair { |k,v| json_msg_opts[k.to_sym] = v } end - result = self.new(struct['msg_type'], json_msg_opts, ic_opts.merge(json_opts)) - result.add(struct['msg']) + result = self.new(ic_opts.merge(json_opts)) + + ['header','trailer'].each { |envseg| + if struct[envseg] + target = result.send(envseg.to_sym) + struct[envseg].last.each_pair { |de,val| + if val.is_a?(Hash) + val.each_pair { |cde,sval| + target[de][0][cde][0].value = sval + } + else + target[de][0].value = val + end + } + end + } + + struct['body'].each { |msg_def| + msg_def.each_pair { |msg_type, msg_body| + if unh = msg_body.find { |s| s[0] == 'UNH' } + version_info = unh[1]['S009'] + json_msg_opts[:resp_agency] = version_info['0051'] + json_msg_opts[:version] = version_info['0052'] + json_msg_opts[:release] = version_info['0054'] + end + result.add_message(msg_type, json_msg_opts) + result.add(msg_body) + } + } result.finalize end - def initialize(msg_type, msg_opts = {}, ic_opts = {}) + def initialize(ic_opts = {}) # Bug in edi4r 0.9 -- sometimes :recipient is used; sometimes :recip. It doesn't # work. We'll override it. local_ic_opts = ic_opts.reject { |k,v| [:sender,:sender_qual,:recipient,:recipient_qual].include?(k) } @ic = EDI::E::Interchange.new(local_ic_opts || {}) # Apply any envelope defaults. - ['UNA','UNB','UNZ'].each { |seg| + ['UNB','UNZ'].each { |seg| seg_defs = self.class.defaults[seg] if seg_defs seg_defs.each_pair { |cde,defs| @@ -136,7 +163,9 @@ module EDI::E @ic.header.cS002.d0007 = ic_opts[:sender_qual] unless ic_opts[:sender_qual].nil? @ic.header.cS003.d0010 = ic_opts[:recipient] unless ic_opts[:recipient].nil? @ic.header.cS003.d0007 = ic_opts[:recipient_qual] unless ic_opts[:recipient_qual].nil? - + end + + def add_message(msg_type, msg_opts = {}) @message = @ic.new_message( { :msg_type => msg_type, :version => 'D', :release => '96A', :resp_agency => 'UN' }.merge(msg_opts || {}) ) @ic.add(@message,false) end @@ -166,16 +195,26 @@ module EDI::E @ic.to_s end + def method_missing(sym, *args) + if @ic.respond_to?(sym) + @ic.send(sym, *args) + else + super(sym, *args) + end + end + private def add_segment(seg_name, value) if seg_name =~ /^[A-Z]{3}$/ - seg = @message.new_segment(seg_name) - @message.add(seg) - default = self.class.defaults[seg_name] - data = default.nil? ? value : default.merge(value) - data.each_pair { |de,val| - add_element(seg,de,val,default) - } + if seg_name !~ /^UN[HT]$/ + seg = @message.new_segment(seg_name) + @message.add(seg) + default = self.class.defaults[seg_name] + data = default.nil? ? value : default.merge(value) + data.each_pair { |de,val| + add_element(seg,de,val,default) + } + end else apply_mapping(seg_name, value) end @@ -184,7 +223,9 @@ module EDI::E def add_element(parent, de, value, default) default = default[de] unless default.nil? - if value.is_a?(Hash) + if de =~ /^SG[0-9]+$/ + value.each { |v| self.add(*v) } + elsif value.is_a?(Hash) new_parent = parent.send("c#{de}") data = default.nil? ? value : default.merge(value) data.each_pair { |k,v| add_element(new_parent,k,v,default) } diff --git a/test/map_spec.rb b/test/map_spec.rb index 447c48258..02975afde 100644 --- a/test/map_spec.rb +++ b/test/map_spec.rb @@ -5,7 +5,8 @@ require 'edi/edi2json' describe EDI::E::Mapper do before(:each) do - @map = EDI::E::Mapper.new('ORDERS') + @map = EDI::E::Mapper.new + @map.add_message('ORDERS') end it "should chunk text" do @@ -42,7 +43,8 @@ describe EDI::E::Mapper do end it "should properly fill in interchange envelope defaults" do - map = EDI::E::Mapper.new('ORDERS', nil, {:sender => '123456', :recipient => '654321'}) + map = EDI::E::Mapper.new({:sender => '123456', :recipient => '654321'}) + map.add_message('ORDERS') map.to_s.should =~ /UNA:\+\.\? 'UNB\+UNOB:3\+123456\+654321\+[0-9]{6}:[0-9]{4}\+1'UNH\+1\+ORDERS:D:96A:UN'UNT\+2\+1'UNZ\+1\+1'/ end @@ -112,7 +114,7 @@ describe EDI::E::Mapper do } # Can't compare everything because of timestamping, so we'll just compare # the bodies for a high degree of confidence - interchange.to_hash['body'].should == [["ORDERS", {"trailer"=>["UNT", {"0074"=>33, "0062"=>"1"}], "body"=>[["BGM", {"C002"=>{"1001"=>"220"}, "1225"=>"9", "1004"=>"2"}], ["DTM", {"C507"=>{"2005"=>"137", "2380"=>"20090331", "2379"=>"102"}}], ["NAD", {"C082"=>{"3039"=>"3472205", "3055"=>"91"}, "SG2"=>[["RFF", {"C506"=>{"1153"=>"API", "1154"=>"3472205 0001"}}]], "3035"=>"BY"}], ["NAD", {"C082"=>{"3039"=>"3472205", "3055"=>"31B"}, "SG2"=>[["RFF", {"C506"=>{"1153"=>"API", "1154"=>"3472205 0001"}}]], "3035"=>"BY"}], ["NAD", {"C082"=>{"3039"=>"1556150", "3055"=>"31B"}, "3035"=>"SU"}], ["NAD", {"C082"=>{"3039"=>"1556150", "3055"=>"91"}, "SG2"=>[["RFF", {"C506"=>{"1153"=>"IA", "1154"=>"1865"}}]], "3035"=>"SU"}], ["CUX", {"C504"=>{"6345"=>"USD", "6347"=>"2", "6343"=>"9"}}], ["LIN", {"SG25"=>[["PIA", {"C212"=>{"7140"=>"03-0010837", "7143"=>"SA"}, "4347"=>"5"}], ["IMD", {"C273"=>{"7008"=>"Discernment"}, "7077"=>"F", "7081"=>"BTI"}], ["IMD", {"C273"=>{"7008"=>"Concord Records,"}, "7077"=>"F", "7081"=>"BPU"}], ["IMD", {"C273"=>{"7008"=>"1986."}, "7077"=>"F", "7081"=>"BPD"}], ["IMD", {"C273"=>{"7008"=>"1 sound disc :"}, "7077"=>"F", "7081"=>"BPH"}], ["QTY", {"C186"=>{"6060"=>2, "6063"=>"21"}}], ["PRI", {"C509"=>{"5125"=>"AAB", "5118"=>35.95}}], ["RFF", {"C506"=>{"1153"=>"LI", "1154"=>"2/1"}}]], "1082"=>1}], ["LIN", {"SG25"=>[["PIA", {"C212"=>{"7140"=>"03-0010840", "7143"=>"SA"}, "4347"=>"5"}], ["IMD", {"C273"=>{"7008"=>"The inner source"}, "7077"=>"F", "7081"=>"BTI"}], ["IMD", {"C273"=>{"7008"=>"Duke, George, 1946-"}, "7077"=>"F", "7081"=>"BAU"}], ["IMD", {"C273"=>{"7008"=>"MPS Records,"}, "7077"=>"F", "7081"=>"BPU"}], ["IMD", {"C273"=>{"7008"=>"1973."}, "7077"=>"F", "7081"=>"BPD"}], ["IMD", {"C273"=>{"7008"=>"2 sound discs :"}, "7077"=>"F", "7081"=>"BPH"}], ["QTY", {"C186"=>{"6060"=>1, "6063"=>"21"}}], ["PRI", {"C509"=>{"5125"=>"AAB", "5118"=>28.95}}], ["RFF", {"C506"=>{"1153"=>"LI", "1154"=>"2/2"}}]], "1082"=>2}], ["UNS", {"0081"=>"S"}], ["CNT", {"C270"=>{"6069"=>"2", "6066"=>2}}], ["UNT", {"0074"=>33, "0062"=>"1"}]], "header"=>["UNH", {"S009"=>{"0052"=>"D", "0065"=>"ORDERS", "0054"=>"96A", "0051"=>"UN"}, "0062"=>"1"}]}]] + interchange.to_hash['body'].should == [{"ORDERS"=>[["UNH", {"S009"=>{"0052"=>"D", "0054"=>"96A", "0065"=>"ORDERS", "0051"=>"UN"}, "0062"=>"1"}], ["BGM", {"1225"=>"9", "C002"=>{"1001"=>"220"}, "1004"=>"2"}], ["DTM", {"C507"=>{"2379"=>"102", "2380"=>"20090331", "2005"=>"137"}}], ["NAD", {"C080"=>{"3036"=>[]}, "C058"=>{"3124"=>[]}, "C059"=>{"3042"=>[]}, "C082"=>{"3039"=>"3472205", "3055"=>"91"}, "SG2"=>[["RFF", {"C506"=>{"1153"=>"API", "1154"=>"3472205 0001"}}]], "3035"=>"BY"}], ["NAD", {"C080"=>{"3036"=>[]}, "C058"=>{"3124"=>[]}, "C059"=>{"3042"=>[]}, "C082"=>{"3039"=>"3472205", "3055"=>"31B"}, "SG2"=>[["RFF", {"C506"=>{"1153"=>"API", "1154"=>"3472205 0001"}}]], "3035"=>"BY"}], ["NAD", {"C080"=>{"3036"=>[]}, "C058"=>{"3124"=>[]}, "C059"=>{"3042"=>[]}, "C082"=>{"3039"=>"1556150", "3055"=>"31B"}, "3035"=>"SU"}], ["NAD", {"C080"=>{"3036"=>[]}, "C058"=>{"3124"=>[]}, "C059"=>{"3042"=>[]}, "C082"=>{"3039"=>"1556150", "3055"=>"91"}, "SG2"=>[["RFF", {"C506"=>{"1153"=>"IA", "1154"=>"1865"}}]], "3035"=>"SU"}], ["CUX", {"C504"=>[{"6345"=>"USD", "6347"=>"2", "6343"=>"9"}]}], ["LIN", {"SG25"=>[["PIA", {"C212"=>[{"7140"=>"03-0010837", "7143"=>"SA"}], "4347"=>"5"}], ["IMD", {"C273"=>{"7008"=>["Discernment"]}, "7077"=>"F", "7081"=>"BTI"}], ["IMD", {"C273"=>{"7008"=>["Concord Records,"]}, "7077"=>"F", "7081"=>"BPU"}], ["IMD", {"C273"=>{"7008"=>["1986."]}, "7077"=>"F", "7081"=>"BPD"}], ["IMD", {"C273"=>{"7008"=>["1 sound disc :"]}, "7077"=>"F", "7081"=>"BPH"}], ["QTY", {"C186"=>{"6060"=>2, "6063"=>"21"}}], ["PRI", {"C509"=>{"5125"=>"AAB", "5118"=>35.95}}], ["RFF", {"C506"=>{"1153"=>"LI", "1154"=>"2/1"}}]], "1082"=>1}], ["LIN", {"SG25"=>[["PIA", {"C212"=>[{"7140"=>"03-0010840", "7143"=>"SA"}], "4347"=>"5"}], ["IMD", {"C273"=>{"7008"=>["The inner source"]}, "7077"=>"F", "7081"=>"BTI"}], ["IMD", {"C273"=>{"7008"=>["Duke, George, 1946-"]}, "7077"=>"F", "7081"=>"BAU"}], ["IMD", {"C273"=>{"7008"=>["MPS Records,"]}, "7077"=>"F", "7081"=>"BPU"}], ["IMD", {"C273"=>{"7008"=>["1973."]}, "7077"=>"F", "7081"=>"BPD"}], ["IMD", {"C273"=>{"7008"=>["2 sound discs :"]}, "7077"=>"F", "7081"=>"BPH"}], ["QTY", {"C186"=>{"6060"=>1, "6063"=>"21"}}], ["PRI", {"C509"=>{"5125"=>"AAB", "5118"=>28.95}}], ["RFF", {"C506"=>{"1153"=>"LI", "1154"=>"2/2"}}]], "1082"=>2}], ["UNS", {"0081"=>"S"}], ["CNT", {"C270"=>{"6069"=>"2", "6066"=>2}}], ["UNT", {"0074"=>33, "0062"=>"1"}]]}] end end \ No newline at end of file diff --git a/test/openils_map_spec.rb b/test/openils_map_spec.rb index 5cbf73f8f..04db02b68 100644 --- a/test/openils_map_spec.rb +++ b/test/openils_map_spec.rb @@ -3,7 +3,8 @@ require 'openils/mapper' describe OpenILS::Mapper do before(:each) do - @map = OpenILS::Mapper.new('ORDERS') + @map = OpenILS::Mapper.new + @map.add_message('ORDERS') end it "should add both qualified and unqualified buyer/vendor fields" do @@ -32,7 +33,7 @@ describe OpenILS::Mapper do it "should create a message from high-level JEDI input" do json = File.read(File.join(File.dirname(__FILE__), 'test_po.json')) - @map = OpenILS::Mapper.from_json(%{{ "msg_type": "ORDERS", "msg": #{json}, "sender": "123456", "recipient": {"id": "999999999", "id-qualifier": "1"}}}) + @map = OpenILS::Mapper.from_json(%{{ "body": [{ "ORDERS": #{json}}], "sender": "123456", "recipient": {"id": "999999999", "id-qualifier": "1"}}}) @map.message.to_s.should == "UNH+1+ORDERS:D:96A:UN'BGM+220+2+9'DTM+137:20090331:102'NAD+BY+3472205::91'RFF+API:3472205 0001'NAD+BY+3472205::31B'RFF+API:3472205 0001'NAD+SU+1556150::31B'NAD+SU+1556150::91'RFF+IA:1865'CUX+2:USD:9'LIN+1'PIA+5+03-0010837:SA'IMD+F+BTI+:::Discernment'IMD+F+BPU+:::Concord Records,'IMD+F+BPD+:::1986.'IMD+F+BPH+:::1 sound disc ?:'QTY+21:2'PRI+AAB:35.95'RFF+LI:2/1'LIN+2'PIA+5+03-0010840:SA'IMD+F+BTI+:::The inner source'IMD+F+BAU+:::Duke, George, 1946-'IMD+F+BPU+:::MPS Records,'IMD+F+BPD+:::1973.'IMD+F+BPH+:::2 sound discs ?:'QTY+21:1'PRI+AAB:28.95'RFF+LI:2/2'UNS+S'CNT+2:2'UNT+33+1'" @map.header.cS002.to_s.should == "123456:31B" @map.header.cS003.to_s.should == "999999999:1" -- 2.11.0