# frozen_string_literal: true require 'strscan' module TacviewClient class Reader # Parses an event coming out of the Tacview server when they are # too complicated for simple extraction. # # Not to be used directly by library users, should only be called # by an instance of the Reader class # # See https://www.tacview.net/documentation/acmi/en/ for information # of the Tacview ACMI format this module is parsing # @private class Parser HEX_NUMBER = /\h+/.freeze OPTIONALLY_DECIMAL_NUMBER = /-?\d+\.?\d+/.freeze POSITION_START_INDICATOR = /T=/.freeze POSITION_SEPARATOR = /\|/.freeze FIELD_SEPARATOR = /,/.freeze END_OF_FILE = /$/.freeze END_OF_FIELD = Regexp.union(FIELD_SEPARATOR, END_OF_FILE) # Parse an ACMI line associated with the update of an object. def parse_object_update(line) @scanner = StringScanner.new(line) @result = {} parse_object_id return nil if own_vehicle_message? parse_lon_lat_alt parse_heading parse_fields @result end private def parse_object_id @result[:object_id] = @scanner.scan(HEX_NUMBER) @scanner.skip FIELD_SEPARATOR end # Updates without a position are always from own vehicle based on looking # through samples. We don't care about these so ignore them def own_vehicle_message? @scanner.peek(2) != POSITION_START_INDICATOR.source end def parse_lon_lat_alt @scanner.skip POSITION_START_INDICATOR %i[longitude latitude altitude].each do |field| value = @scanner.scan(OPTIONALLY_DECIMAL_NUMBER) @result[field] = BigDecimal(value) if value @scanner.skip POSITION_SEPARATOR end end def parse_heading return if end_of_message? # Check to see if the heading (9th field which is 4 more # separators from our current position) is present if @scanner.check_until(END_OF_FIELD) .count(POSITION_SEPARATOR.source) == 4 # If it is then we will save that as well by skipping all the # text until the heading value @scanner.scan_until(/\|[\|\-?0-9.]*\|/) heading = @scanner.scan OPTIONALLY_DECIMAL_NUMBER @result[:heading] = BigDecimal(heading) if heading end @scanner.scan_until END_OF_FIELD end def parse_fields until end_of_message? field = @scanner.scan_until END_OF_FIELD field.chomp! FIELD_SEPARATOR.source key, value = field.split('=', 2) @result[key.downcase.to_sym] = value end end def end_of_message? @scanner.eos? end end end end