class EDI::E::Interchange

Interchange: Class of the top-level objects of UN/EDIFACT data

Attributes

charset[R]
groups_created[R]
messages_created[R]
show_una[RW]
una[R]

Public Class Methods

new( user_par={} ) click to toggle source

Create an empty UN/EDIFACT interchange

Supported parameters (passed hash-style):

Essentials, should not be changed later

:charset

Sets S001.0001, default = ‘UNOB’

:version

Sets S001.0002, default = 3

:i_edi

Interactive EDI mode, a boolean (UIB instead of UNB …), default = false

Optional parameters affecting to_s, with corresponding setters

:show_una

Adds UNA sement to output, default = true

:output_mode

See setter output_mode=(), default = :verbatim

:una_string

See class UNA for setters, default = nil

Optional UNB presets for your convenience, may be changed later

:sender

Presets DE S002/0004, default = nil

:recipient

Presets DE S003/0010, default = nil

:interchange_control_reference

Presets DE 0020, default = ‘1’

:application_reference

Presets DE 0026, default = nil

:interchange_agreement_id

Presets DE 0032, default = nil

:acknowledgment_request

Presets DE 0031, default = nil

:test_indicator

Presets DE 0035, default = nil

Notes

  • Date and time in S004 are set to the current values automatically.

  • Add or change any data element later, except those in S001.

Examples:

Calls superclass method EDI::Interchange::new
# File lib/edi4r/edifact.rb, line 372
def initialize( user_par={} )
  super( user_par ) # just in case...
  if (illegal_keys = user_par.keys - @@interchange_default_keys) != []
    msg = "Illegal parameter(s) found: #{illegal_keys.join(', ')}\n"
    msg += "Valid param keys (symbols): #{@@interchange_default_keys.join(', ')}"
    raise ArgumentError, msg
  end
  par = @@interchange_defaults.merge( user_par )

  @messages_created = @groups_created = 0

  @syntax = 'E' # par[:syntax]      # E = UN/EDIFACT
  @e_iedi = par[:i_edi]
  @charset = par[:charset]
  @version = par[:version]
  @una = UNA.new(self, par[:una_string])
  self.output_mode = par[:output_mode]
  self.show_una = par[:show_una]

  check_consistencies
  init_ndb( @version )

  if @e_iedi  # Interactive EDI

    raise "I-EDI not supported yet"

    # Fill in what we already know about I-EDI:

    @header = new_segment('UIB')
    @trailer = new_segment('UIZ')
    @header.cS001.d0001 = par[:charset]
    @header.cS001.d0002 = par[:version]

    @header.cS002.d0004 = par[:sender] unless par[:sender].nil?
    @header.cS003.d0010 = par[:recipient] unless par[:recipient].nil?
    @header.cS302.d0300 = par[:interchange_control_reference]
    # FIXME: More to do in S302...

    x= :test_indicator;           @header.d0035 = par[x] unless par[x].nil?

    t = Time.now
    @header.cS300.d0338 = t.strftime(par[:version]==4 ? '%Y%m%d':'%y%m%d')
    @header.cS300.d0314 = t.strftime("%H%M")
    
    @trailer.d0036 = 0
    ch, ct = @header.cS302, @trailer.cS302
    ct.d0300, ct.d0303, ct.d0051, ct.d0304 = ch.d0300, ch.d0303, ch.d0051, ch.d0304 

  else # Batch EDI

    @header = new_segment('UNB')
    @trailer = new_segment('UNZ')
    @header.cS001.d0001 = par[:charset]
    @header.cS001.d0002 = par[:version]
    @header.cS002.d0004 = par[:sender] unless par[:sender].nil?
    @header.cS003.d0010 = par[:recipient] unless par[:recipient].nil?
    @header.d0020 = par[:interchange_control_reference]

    x= :application_reference;    @header.d0026 = par[x] unless par[x].nil?
    x= :acknowledgment_request;   @header.d0031 = par[x] unless par[x].nil?
    x= :interchange_agreement_id; @header.d0032 = par[x] unless par[x].nil?
    x= :test_indicator;           @header.d0035 = par[x] unless par[x].nil?

    t = Time.now
    @header.cS004.d0017 = t.strftime(par[:version]==4 ? '%Y%m%d':'%y%m%d')
    @header.cS004.d0019 = t.strftime("%H%M")
    
    @trailer.d0036 = 0
  end
end
parse( hnd=$stdin, auto_validate=true ) click to toggle source

Reads EDIFACT data from given stream (default: $stdin), parses it and returns an Interchange object

# File lib/edi4r/edifact.rb, line 447
def Interchange.parse( hnd=$stdin, auto_validate=true )
  builder = StreamingBuilder.new( auto_validate )
  builder.go( hnd )
  builder.interchange
end
parse_xml( xdoc ) click to toggle source

Returns a REXML document that represents the interchange

xdoc

REXML document that contains the XML representation of a UN/EDIFACT interchange

# File lib/edi4r/edifact-rexml.rb, line 34
def Interchange.parse_xml( xdoc )
  _root = xdoc.root
  _header  = _root.elements["Header"]
  _trailer = _root.elements["Trailer"]
  _una  = _header.elements["Parameter[@name='UNA']"]
  _una = _una.text.strip if _una
  raise "Empty UNA" if _una and _una.empty? # remove later!
  # S001: Works for both batch and interactive EDI:
  _s001 =  _header.elements["Segment/CDE[@name='S001']"]
  _version = _s001.elements["DE[@name='0002']"].text.to_i
  _charset = _s001.elements["DE[@name='0001']"].text.strip
  params = { :charset => _charset, :version => _version }
  if _una
    params[:una_string] = _una
    params[:show_una] = true
  end
  ic = Interchange.new( params )
  if _root.elements["Message"].nil? # correct ??
    _root.elements.each('MsgGroup') do |xel|
      ic.add( MsgGroup.parse_xml( ic, xel ), false )
    end
  else
    _root.elements.each('Message') do |xel|
      ic.add( Message.parse_xml( ic, xel ), false )
    end
  end

  ic.header  = Segment.parse_xml( ic, _header.elements["Segment"] )
  ic.trailer = Segment.parse_xml( ic, _trailer.elements["Segment"] )
  ic.validate
  ic
end
peek(hnd=$stdin, params={}) click to toggle source

Read maxlen bytes from $stdin (default) or from given stream (UN/EDIFACT data expected), and peek into first segment (UNB/UIB).

Returns an empty Interchange object with a properly header filled.

Intended use:

Efficient routing by reading just UNB data: sender/recipient/ref/test
# File lib/edi4r/edifact.rb, line 462
def Interchange.peek(hnd=$stdin, params={}) # Handle to input stream
  builder = StreamingBuilder.new( false )
  if params[:deep_peek]
    def builder.on_segment( s, tag )
    end
  else
    def builder.on_ung( s )
      throw :done
    end
    def builder.on_unh_uih( s, tag )
      throw :done  # FIXME: UNZ??
    end
  end
  builder.go( hnd )
  builder.interchange
end
peek_xml(xdoc) click to toggle source

Read maxlen bytes from $stdin (default) or from given stream (UN/EDIFACT data expected), and peek into first segment (UNB/UIB).

Returns an empty Interchange object with a properly header filled.

Intended use:

Efficient routing by reading just UNB data: sender/recipient/ref/test
# File lib/edi4r/edifact-rexml.rb, line 76
def Interchange.peek_xml(xdoc) # Handle to REXML document
  _root = xdoc.root
  _header  = _root.elements["Header"]
  _trailer = _root.elements["Trailer"]
  _una  = _header.elements["Parameter[@name='UNA']"]
  _una = _una.text if _una
  raise "Empty UNA" if _una and _una.empty? # remove later!
  # S001: Works for both batch and interactive EDI:
  _s001 =  _header.elements["Segment/CDE[@name='S001']"]
  _version = _s001.elements["DE[@name='0002']"].text.to_i
  _charset = _s001.elements["DE[@name='0001']"].text.strip
  params = { :charset => _charset, :version => _version }
  if _una
    params[:una_string] = _una
    params[:show_una] = true
  end
  ic = Interchange.new( params )

  ic.header  = Segment.parse_xml( ic, _header.elements["Segment"] )
  ic.trailer = Segment.parse_xml( ic, _trailer.elements["Segment"] )

  ic
end

Public Instance Methods

add( obj, auto_validate=true ) click to toggle source

Add either a MsgGroup or Message object to the interchange. Note: Don’t mix both types!

UNZ/UIZ counter DE 0036 is automatically incremented.

Calls superclass method EDI::Interchange#add
# File lib/edi4r/edifact.rb, line 521
def add( obj, auto_validate=true )
  super
  @trailer.d0036 += 1 #if @trailer # @trailer doesn't exist yet when parsing
  # FIXME: Warn/fail if UNH/UIH/UNG id is not unique (at validation?)
end
inspect( indent='', symlist=[] ) click to toggle source

Yields a readable, properly indented list of all contained objects, including the empty ones. This may be a very long string!

Calls superclass method EDI::Collection#inspect
# File lib/edi4r/edifact.rb, line 590
def inspect( indent='', symlist=[] )
  symlist << :una
  super
end
is_iedi?() click to toggle source

Returns true if this is an I-EDI interchange (Interactive EDI)

# File lib/edi4r/edifact.rb, line 483
def is_iedi?
  @e_iedi
end
new_message(params={}) click to toggle source

Derive an empty message from this interchange context. Parameters may be passed hash-like. See Message.new for details

# File lib/edi4r/edifact.rb, line 539
def new_message(params={})
  @messages_created += 1
  Message.new(self, params)
end
new_msggroup(params={}) click to toggle source

Derive an empty message group from this interchange context. Parameters may be passed hash-like. See MsgGroup.new for details

# File lib/edi4r/edifact.rb, line 531
def new_msggroup(params={}) # to be completed ...
  @groups_created += 1
  MsgGroup.new(self, params)
end
output_mode=( value ) click to toggle source

This method modifies the behaviour of method to_s(): UN/EDIFACT interchanges and their components are turned into strings either “verbatim” (default) or in some more readable way. This method corresponds to a parameter with same name at creation time.

Valid values:

:linebreak

One-segment-per-line representation

:indented

Like :linebreak but with additional indentation (2 blanks per hierarchy level).

:verbatim

No linebreak (default), ISO compliant

Calls superclass method
# File lib/edi4r/edifact.rb, line 499
def output_mode=( value )
  super( value )
  @e_linebreak = @e_indent = ''
  case value
  when :verbatim
    # NOP (default)
  when :linebreak
    @e_linebreak = "\n"
  when :indented
    @e_linebreak = "\n"
    @e_indent = '  '
  else
    raise "Unknown output mode '#{value}'. Supported modes: :linebreak, :indented, :verbatim (default)"
  end
end
to_din16557_4( xdoc = REXML::Document.new ) click to toggle source

Returns a REXML document that represents the interchange according to DIN 16557-4

Calls superclass method EDI::Collection_HT#to_din16557_4
# File lib/edi4r/edifact-rexml.rb, line 122
def to_din16557_4( xdoc = REXML::Document.new )
  externalID = "SYSTEM \"edifact.dtd\""
  doc_element_name = 'EDIFACTINTERCHANGE'
  xdoc << REXML::XMLDecl.new
  xdoc << REXML::DocType.new( doc_element_name, externalID )

  doc_el = REXML::Element.new( doc_element_name )
  xel  = REXML::Element.new( 'UNA' ) 
  xel.attributes["UNA1"]  = una.ce_sep.chr
  xel.attributes["UNA2"]  = una.de_sep.chr
  xel.attributes["UNA3"]  = una.decimal_sign.chr
  xel.attributes["UNA4"]  = una.esc_char.chr
  xel.attributes["UNA5"]  = una.rep_sep.chr
  xel.attributes["UNA6"]  = una.seg_term.chr
  xdoc.elements << doc_el
  doc_el.elements << xel

  super( xdoc.root )
  xdoc
end
to_s() click to toggle source

Returns the string representation of the interchange.

Type conversion and escaping are provided. The UNA object is shown when show_una is set to true . See output_mode for modifiers.

Calls superclass method EDI::Collection_HT#to_s
# File lib/edi4r/edifact.rb, line 580
def to_s
  s = show_una ? una.to_s + @e_linebreak : ''
  postfix = '' << una.seg_term << @e_linebreak
  s << super( postfix )
end
to_xml( xdoc = REXML::Document.new ) click to toggle source

Returns a REXML document that represents the interchange

Calls superclass method EDI::Interchange#to_xml
# File lib/edi4r/edifact-rexml.rb, line 104
    def to_xml( xdoc = REXML::Document.new )
      rc = super
      # Add parameter(s) to header in rc[1]
      unless @una.nil? #@una.empty?
        xel = REXML::Element.new('Parameter')
        rc[1] << xel
        xel.attributes["name"] = 'UNA'
        xel.text = @una.to_s
      end
#      rc
      xdoc
    end
validate( err_count=0 ) click to toggle source

Returns the number of warnings found, writes warnings to STDERR

Calls superclass method EDI::Collection_HT#validate
# File lib/edi4r/edifact.rb, line 598
def validate( err_count=0 )
  if (h=self.size) != (t=@trailer.d0036)
    warn "Counter UNZ/UIZ, DE0036 does not match content: #{t} vs. #{h}"
    err_count += 1
  end
  if (h=@header.cS001.d0001) != @charset
    warn "Charset UNZ/UIZ, S001/0001 mismatch: #{h} vs. #@charset"
    err_count += 1
  end
  if (h=@header.cS001.d0002) != @version
    warn "Syntax version UNZ/UIZ, S001/0002 mismatch: #{h} vs. #@version"
    err_count += 1
  end
  check_consistencies

  if is_iedi?
    if (t=@trailer.cS302.d0300) != (h=@header.cS302.d0300)
      warn "UIB/UIZ mismatch in initiator ref (S302/0300): #{h} vs. #{t}"
      err_count += 1
    end
    # FIXME: Add more I-EDI checks
  else
    if (t=@trailer.d0020) != (h=@header.d0020)
      warn "UNB/UNZ mismatch in refno (DE0020): #{h} vs. #{t}"
      err_count += 1
    end
  end

  # FIXME: Check if messages/groups are uniquely numbered

  super
end

Private Instance Methods

check_consistencies() click to toggle source

Private method: Check if basic UNB elements are set properly

# File lib/edi4r/edifact.rb, line 646
def check_consistencies
  # FIXME - @syntax should be completely avoided, use sub-module name
  if not ['E'].include?(@syntax) # More anticipated here
    raise "#{@syntax} - syntax not supported!"
  end
  case @version
  when 1
    if @charset != 'UNOA'
      raise "Syntax version 1 permits only charset UNOA!"
    end
  when 2
    if not @charset =~ /UNO[AB]/
      raise "Syntax version 2 permits only charsets UNOA, UNOB!"
    end
  when 3
    if not @charset =~ /UNO[A-F]/
      raise "Syntax version 3 permits only charsets UNOA...UNOF!"
    end
  when 4
    # A,B: ISO 646 subsets, C-K: ISO-8859-x, X: ISO 2022, Y: ISO 10646-1
    if not @charset =~ /UNO[A-KXY]/
      raise "Syntax version 4 permits only charsets UNOA...UNOZ!"
    end
  else
    raise "#{@version} - no such syntax version!"
  end
  if @e_iedi and @version != 4
    raise "Inconsistent parameters - I-EDI requires syntax version 4!"
  end
  @illegal_charset_pattern = Illegal_Charset_Patterns['@version']
  # Add more rules ...
end
init_ndb(d0002, d0076 = nil) click to toggle source

Private method: Loads EDIFACT norm database

# File lib/edi4r/edifact.rb, line 636
def init_ndb(d0002, d0076 = nil)
  @basedata = EDI::Dir::Directory.create(root.syntax,
                                         :d0002   => @version, 
                                         :d0076   => d0076, 
                                         :is_iedi => is_iedi?)
end