; $Id: read_ascii_cmdline.pro 12128 2013-04-22 22:31:56Z jwl $ ; ; Copyright (c) 1996-2008, ITT Visual Information Solutions. All ; rights reserved. Unauthorized reproduction is prohibited. ;+ ; NAME: ; READ_ASCII_CMDLINE ; ; PURPOSE: ; Read data from an ASCII file into IDL. ; ; CATEGORY: ; Input/Output. ; ; CALLING SEQUENCE: ; data = READ_ASCII(file) ; ; INPUTS: ; file - Name of file to read. ; ; INPUT KEYWORD PARAMETERS: ; record_start - 1st sequential "record" (see DESCRIPTION) to read. ; Default = 0 (the first record of the file). ; num_records - Number of records to read. ; Default = 0 = Read up to and including the last record. ; ; template - ASCII file template (e.g., generated by function ; ASCII_TEMPLATE) describing attributes of the file ; to read. Specific attributes contained in the ; template may be overridden by keywords below. ; Default = (see the keywords below). ; ; start_line - Number of lines of header to skip. ; Default (if no template) = 0L. (NOTE: this keyword name ; has been changed from the IDL read_ascii routine. It was data_start.) ; delimiter - Character that delimits fields. ; Default (if no template) = '' = use fields(*).loc. ; missing_value - Value to replace any missing/invalid data. ; Default (if no template) = !VALUES.F_NAN. ; comment_symbol - String identifying comments ; (from comment_symbol to the next end-of-line). ; Default (if no template) = '' = no comments. ; ; field_types - An array of the field (column) IDL data type. This array can be ; either a string array or a long array. Example: ['string', 'double', 'double'] ; or [7,5,5]. Valid types are ['int', 'long', 'float', 'double', 'string', ; 'structure'] or [2, 3, 4, 5, 7, 8] ; field_names - An array of the names for each field or column. Default values are ; Field01, Field02, Field03, ..... Fieldnn. ; field_locations - An array of the start positions of each column. Default values are ; zero and the procedure will try to figure it out. ; field_groups - An array of fields that are common to a group. ; ; [Note: The 'fields' keyword has not been implemented yet.] ; fields - Descriptions of the data fields, formatted as ; an array of structures containing the tags: ; name = name of the field (string) ; type = type of field as returned by SIZE (long) ; loc = offset from the beginning of line to ; the start of the field (long) ; group = sequential group the field is in (int) ; Default (if no template) = ; {name:'field', type:4L, loc:0L, group:0}. ; ; verbose - If set, print runtime messages. ; Default = Do not print them. ; ; OUTPUT KEYWORD PARAMETERS: ; header - The header read (string array of length ; data_start). If no header, empty string returned. ; ; count - The number of records read. ; ; OUTPUTS: ; The function returns an anonymous structure, where each field in ; the structure is a "field" of the data read (see DESCRIPTION). ; If no records are read, 0 is returned. ; ; COMMON BLOCKS: ; None. ; ; SIDE EFFECTS: ; None. ; ; RESTRICTIONS: ; See DESCRIPTION. ; ; DESCRIPTION: ; ASCII files handled by this routine consist of an optional header ; of a fixed number of lines, followed by columnar data. Files may ; also contain comments, which exist between a user-specified comment ; string and the corresponding end-of-line. ; ; One or more rows of data constitute a "record." Each data element ; within a record is considered to be in a different column, or "field." ; Adjacent fields may be "grouped" into multi-column fields. ; The data in one field must be of, or promotable to, a single ; type (e.g., FLOAT). ; ; EXAMPLES: ; ; Using default file attributes. ; data = READ_ASCII(file) ; ; ; Setting specific file attributes. ; data = READ_ASCII(file, START_LINE=10) ; ; ; Using a template to define file attributes. ; data = READ_ASCII(file, TEMPLATE=template) ; ; ; Using a template to define file attributes, ; ; and overriding some of those attributes. ; data = READ_ASCII(file, TEMPLATE=template, START_LINE=10) ; ; ; Using the ASCII_TEMPLATE GUI to generate a template in place. ; data = READ_ASCII(file, TEMPLATE=ASCII_TEMPLATE(file)) ; ; [Note: The 'fields' keyword has not been implemented yet.] ; ; An example defining fields by hand. ; fields = REPLICATE({name:'', type:0L, loc:0L, group:0}, 2, 3) ; num = N_ELEMENTS(fields) ; fields(*).name = 'field' + STRTRIM(STRING(INDGEN(num) + 1), 2) ; fields(*).type = REPLICATE(4L, num) ; fields(*).loc = [0L,10L, 0L,15L, 0L,12L] ; fields(*).group = INDGEN(num) ; data = READ_ASCII(file, FIELDS=fields) ; ; [Note: The 'fields' keyword has not been implemented yet.] ; ; Another example defining fields by hand. ; void = {sMyStructName, name:'', type:0L, loc:0L, group:0} ; fields = [ [ {sMyStructName, 'frog', (SIZE(''))(1), 0L, 0}, $ ; {sMyStructName, 'bird', (SIZE(0 ))(1), 15L, 1} ], $ ; [ {sMyStructName, 'fish', (SIZE(0.))(1), 0L, 2}, $ ; {sMyStructName, 'bear', (SIZE(0D))(1), 15L, 3} ], $ ; [ {sMyStructName, 'boar', (SIZE(0B))(1), 0L, 4}, $ ; {sMyStructName, 'nerd', (SIZE(OL))(1), 15L, 5} ] ] ; data = READ_ASCII(file, FIELDS=fields) ; ; DEVELOPMENT NOTES: ; ; - See ???,xxx in the code. ; ; - Error check input 'delimiter' to be a string (not a byte). ; ; - Implement the 'fields' keyword. ; ; MODIFICATION HISTORY: ; AL & RPM, 8/96 - Written. ; PCS, 3/99 - Deploy STRTOK and other new commands. Gain some speed. ; CT, Aug 2003: Free up temp pointers if an error occurs. ; clrussell 10-01-12 - Added five keywords field_count, field_types, field_names, ; field_locations, field_groups. These keywords were added so ; the user could specify these mixed data types without having to ; use a template. ;- ; ----------------------------------------------------------------------------- ; ; Purpose: Parse out values from a line of text which are in columns. ; pro ssl_ra_parse_column_values, line, types, p_vals, rec_count, $ locs, lengths, missingValue, num_fields compile_opt HIDDEN, STRICTARR on_ioerror, column_cast_failed nf1 = num_fields - 1 for i=0, nf1 do begin if (types[i] ne 0) then begin ; (0 == skip field.) token = (i eq nf1) ? STRTRIM(STRMID(line, locs[i]),2) : $ STRTRIM(STRMID(line, locs[i], lengths[i]),2) ; Assign substring to the variable. This will automatically do ; any necessary type conversions. (*p_vals[i])[rec_count] = (STRLEN(token) ne 0) ? token : $ (types[i] eq 7) ? token : missingValue continue column_cast_failed: message, /reset (*p_vals[i])[rec_count] = missingValue endif endfor ; i end ; ----------------------------------------------------------------------------- ; ; Purpose: Parse out values from a line of text which are separated by ; a given delimiter. ; pro ssl_ra_parse_delim_values, line, types, p_vals, rec_count, $ delimit, missing_value, whitespace_delimited compile_opt HIDDEN, STRICTARR ; Remove whitespace from beginning and end. toks = whitespace_delimited ? STRTOK(line, /EXTRACT) : $ STRTRIM(STRTOK(line, delimit, /EXTRACT, /PRESERVE_NULL), 2) length = STRLEN(toks) on_ioerror, delim_cast_failed n_types = N_ELEMENTS(types) n_toks = N_ELEMENTS(toks) nMin1 = (n_types < n_toks) - 1 ; Loop up to the end of the tokens or the number of fields, whichever ; is smaller. Empty fields will be filled in after this loop. for i=0,nMin1 do begin if (types[i] ne 0) then begin ; (0 == skip field.) ; Assign the substring to the variable. This will automatically do ; any necessary type conversions. (*p_vals[i])[rec_count] = (length[i] ne 0) ? toks[i] : $ ((types[i] eq 7) ? toks[i] : missing_value) ; If successful conversion, then continue the loop. continue delim_cast_failed: ; If failed conversion, suppress the error and fill with missing. message, /reset (*p_vals[i])[rec_count] = missing_value endif endfor ; Need to fill in extra fields with missing. if (n_toks lt n_types) then begin for i=n_toks, n_types-1 do begin if (types[i] gt 0) then $ (*p_vals[i])[rec_count] = missing_value endfor endif end ; ssl_ra_parse_delim_values ; ----------------------------------------------------------------------------- ; ; Purpose: Read in the next n lines of text (skipping blank lines and ; commented lines signified by template.commentSymbol at start; ; also throw away comment portions of regular lines). ; function ssl_ra_get_next_record, template, unit, lines ; COMPILE_OPT hidden, strictarr on_ioerror, end_of_file line = '' count = 0 ; Checking for comments... ; if (template.commentSymbol ne '') then begin while (count lt n_elements(lines)) do begin readf, unit, line pos = strpos(line, template.commentSymbol, 0) if (strtrim(line,2) ne '' and pos[0] ne 0) then begin lines[count] = (pos[0] eq -1) ? line : strmid(line,0,pos[0]) count = count + 1 endif endwhile ; NOT checking for comments... ; endif else begin while (count lt n_elements(lines)) do begin readf, unit, line if (strlen(strtrim(line,2)) ne 0) then begin lines[count] = line count = count + 1 endif endwhile endelse return, 0 ; success end_of_file: ; If read failed, suppress message and return EOF. message, /reset return, 1 ; failure, EOF end ; ssl_ra_get_next_record ; ----------------------------------------------------------------------------- ; ; Purpose: Given a template structure, open an ASCII file and parse out the ; numerical and string values based upon the parameters of the ; given template. ; ; (a) white space separates fields lined up in columns ; (b) a delimiter character separates fields ; ; Note: When skipping to the start of the data, blank lines ARE included ; as lines to skip, but once you get to the data, subsequent blank ; lines (as well as comment lines) are ignored. ; ; Function returns an array of pointers to the data read; ; if no data read, 0 is returned. ; function ssl_ra_read_from_templ, $ name, $ ; IN: name of ASCII file to read template, $ ; IN: ASCII file template start_record, $ ; IN: first record to read records_to_read, $ ; IN: number of records to read doVerbose, $ ; IN: 1B = print runtime messages num_fields_read, $ ; OUT: number of fields successfully read fieldNames, $ ; OUT: associated name of each field read rec_count, $ ; OUT: number of records successfully read num_blocks, $ ; OUT: number of blocks of data header=header ; OUT: (opt) header read COMPILE_OPT hidden, strictarr ; Set default numbers. ; num_fields_read = 0 num_blocks = 0L ; Catch errors. catch, error_status if (error_status ne 0) then begin CATCH, /CANCEL MESSAGE, /INFO, 'Unexpected Error: ' + !ERROR_STATE.msg MESSAGE, /RESET rec_count = 0l return, 0 endif ; Open the file. ; openr, unit, name, /get_lun ; Set various parameters. ; blk_size = 1000 ; each block holds this many records blk_count = 500 ; number of blocks we can have blk_grow = 500 current_block = 0L lines_per_record = n_elements(template.fieldCount) num_fields = template.fieldCount tot_num_fields = total(template.fieldCount) types = template.fieldTypes locs = template.fieldLocations ; The length of the last field depends upon the line length, ; so here just make it some arbitrary number. fieldLengths = (n_elements(locs) gt 1) ? [locs[1:*],0] - locs : 0 ; Define an array of variables for each field. ; p_vals = ptrarr(tot_num_fields, blk_count) for i=0, tot_num_fields-1 do $ if (types[i] gt 0) then $ p_vals[i, current_block] = ptr_new(make_array(blk_size, type=types[i])) ; Read the header and skip to the start of the data. ; dataStart = template.dataStart if (dataStart gt 0) then begin if (doVerbose) then $ print, 'Reading header of ' + strtrim(string(dataStart), 2) + $ ' lines ...', format='(A/)' header = strarr(dataStart) readf, unit, header endif else $ header = '' ; Skip to the start of requested data. ; lines = strarr(lines_per_record) if ((doVerbose) and (start_record gt 0)) then $ print, 'Skipping ' + strtrim(string(start_record), 2) + ' records ...', $ format='(A/)' for i = 0L, start_record-1 do $ end_reached = SSL_RA_GET_NEXT_RECORD(template, unit, lines) if template.delimiter eq 32b then begin delim_str = string([32b, 9b]) whitespace_delimited = 1b end else begin delim_str = string(template.delimiter) whitespace_delimited = 0b endelse nRecord1 = (records_to_read gt 0) ? records_to_read-1L : 2147483647L for rec_count = 0L, nRecord1 do begin ; Read a record. ; end_reached = SSL_RA_GET_NEXT_RECORD(template, unit, lines) if (end_reached) then break ; out of the for loop ;xxx if (doVerbose) then $ print, 'Processing sequential record ' + $ strtrim(string(rec_count+1), 2) + ' ...' anchor = 0 rc = rec_count-current_block*blk_size ; For each line in the record... ; for i=0,lines_per_record-1 do begin if (template.delimiter eq 0B) then begin ; nice columned data... ssl_ra_parse_column_values, lines[i], $ types[ anchor:anchor+num_fields[i]-1], $ p_vals[anchor:anchor+num_fields[i]-1, current_block], $ rc, $ locs[ anchor:anchor+num_fields[i]-1], $ fieldLengths[anchor:anchor+num_fields[i]-1], $ template.missingValue, $ num_fields[i] endif else begin ; data separated by a delimiter... ssl_ra_parse_delim_values, lines[i], $ types[ anchor:anchor+num_fields[i]-1], $ p_vals[anchor:anchor+num_fields[i]-1, current_block], $ rc, $ delim_str, $ template.missingValue, $ whitespace_delimited endelse anchor = anchor + num_fields[i] endfor ; i ; If block is now full, ; Allocate and point to a new block ; if ((rec_count+1) mod blk_size eq 0) then begin current_block = current_block + 1 if (current_block eq blk_count) then begin p_vals = [[p_vals], [ptrarr(tot_num_fields, blk_grow)]] blk_count = blk_count + blk_grow endif for i=0, tot_num_fields-1 do if (types[i] gt 0) then $ p_vals[i, current_block] = $ ptr_new(make_array(blk_size, type=types[i])) endif ; new block endfor ; read ; ------------------------------------ free_lun, unit if (doVerbose) then $ print, 'Total records read: ' + strtrim(string(rec_count), 2), $ format='(A/)' ; No records were read. if (rec_count eq 0) then begin PTR_FREE, p_vals return, 0 endif ; If records were read ... ; Set the output arrays to exactly the correct size. ; for i=0, tot_num_fields-1 do begin if (p_vals[i,current_block] ne ptr_new()) then begin if (rec_count gt current_block*blk_size) then begin *p_vals[i,current_block] = $ (*p_vals[i,current_block])[0:rec_count-current_block*blk_size-1] endif else begin ; block is allocated, but empty ptr_free, p_vals[i,current_block] endelse endif endfor if (rec_count gt current_block*blk_size) then begin num_blocks = current_block + 1 endif else begin num_blocks = current_block endelse ; Check the groups array and arrange the output pointers into ; (potentially) groups of 2-D arrays. ; groups = template.fieldGroups ; Don't include any groups which are skipped fields. ; ptr = where(types eq 0, numSkip) for i=0, numSkip-1 do groups[ptr[i]] = max(groups) + 1 ; Concatenate 1-D arrays into multi arrays based upon groupings. ; uptr = uniq(groups, sort(groups)) if (n_elements(uptr) lt n_elements(groups)) then begin for i=0, n_elements(uptr)-1 do begin for b=0, num_blocks-1 do begin lptr = where(groups eq groups[uptr[i]], lcount) if (lcount gt 1) then begin p_new = p_vals[lptr[0],b] for j=1,lcount-1 do begin *p_new = [[temporary(*p_new)],[temporary(*p_vals[lptr[j],b])]] ptr_free, p_vals[lptr[j],b] p_vals[lptr[j],b] = ptr_new() endfor *p_new = transpose(temporary(*p_new)) endif endfor endfor endif ; Return the pointers that contain data, if any. ; and the associated fieldNames for these pointers ; ptr = where(p_vals[*,0] ne ptr_new(), num_fields_read) if (num_fields_read gt 0) then begin ; data successfully read fieldNames = template.fieldNames[ptr] return, p_vals[ptr,*] endif else begin ; no data read rec_count = 0l return, 0 endelse end ; ssl_ra_read_from_templ ; ----------------------------------------------------------------------------- ; ; Purpose: Return 1B if the template is valid, else 0B. ; function ssl_ra_valid_template, $ template, $ ; IN: template to check message ; OUT: error message if the template is not valid COMPILE_OPT hidden, strictarr message = '' ; Make sure it's a structure. ; sz = size(template) if (sz[sz[0]+1] ne 8) then begin message = 'Template is not a structure.' RETURN, 0B endif ; Get tag names and make sure version field is present. ; tagNamesFound = TAG_NAMES(template) void = WHERE(tagNamesFound eq 'VERSION', count) if (count ne 1) then begin message = 'Version field missing from template.' RETURN, 0B endif ; Do checking based on version. ; case (template.version) of 1.0: begin ; Set the names of the required tags (version alread checked). ; tagNamesRequired = STRUPCASE([ $ 'dataStart', 'delimiter', 'missingValue', 'commentSymbol', $ 'fieldCount', 'fieldTypes', 'fieldNames', 'fieldLocations', $ 'fieldGroups']) ; Check that all of the required tags are present. ; for seqTag = 0, N_ELEMENTS(tagNamesRequired)-1 do begin tag = tagNamesRequired[seqTag] void = WHERE(tagNamesFound eq tag, count) if (count ne 1) then begin message = tag + ' field missing from template.' RETURN, 0B endif endfor end else: begin message = 'The only recognized template version is 1.0 (float).' RETURN, 0B end endcase ; Check for valid structure tag names before we try to ; read the file. for i=0,N_ELEMENTS(template.fieldNames)-1 do begin if (IDL_VALIDNAME(template.fieldNames[i], /CONVERT_SPACES) eq '') then begin message = 'Illegal field name: ' + template.fieldNames[i] return, 0b ; failure endif endfor ; Return that the template is valid. ; RETURN, 1B end ; ssl_ra_valid_template ; ----------------------------------------------------------------------------- ; ; Purpose: Convert to string and remove extra white space. ; function ssl_ra_stringit, value COMPILE_OPT hidden, strictarr result = STRTRIM( STRCOMPRESS( STRING(value) ), 2 ) num = N_ELEMENTS(result) if (num le 1) then RETURN, result ; If two or more values, concatenate them. ; delim = ' ' ret = result[0] for i = 1, num-1 do $ ret = ret + delim + result[i] RETURN, ret end ; ssl_ra_stringit ; ----------------------------------------------------------------------------- ; ; Purpose: Guess at the number of columns in an ASCII file. ; function ssl_ra_guess_columns, fname, dataStart, commentSymbol, delimiter COMPILE_OPT hidden, strictarr on_error, 2 ; Return to caller on error. catch, err_stat if err_stat ne 0 then begin catch, /cancel if n_elements(lun) gt 0 then $ if (lun ne 0) then free_lun, lun message, !error_state.msg endif get_lun, lun openr, lun, fname if dataStart gt 0 then begin header = strarr(dataStart) readf, lun, header end line = '' end_reached = SSL_RA_GET_NEXT_RECORD({commentSymbol: commentSymbol}, $ lun, line) if end_reached then $ message, 'No columns found.' if delimiter eq ' ' then $ positions = strtok(line) $ else $ positions = strtok(line, delimiter, /preserve_null) close, lun free_lun, lun return, n_elements(positions) end ; ----------------------------------------------------------------------------- ; ; Purpose: Check that the input filename is a string, exists, and appears ; to be ASCII. ; function ssl_ra_check_file, fname COMPILE_OPT hidden, strictarr if (SIZE(fname, /TYPE) ne 7) then $ return, -1 ; filename isn't a string info = FILE_INFO(fname) if (~info.exists) then $ return, -2 if (~info.read) then $ return, -3 success = QUERY_ASCII(fname) return, success ? 1 : -4 end ; ------------------------------------------------------------------------ ; Use a recursive approach to constructing the result structure. ; We used to do this by just concatanating each field onto the end of the ; existing structure, but this is extremely inefficient, as the entire ; structure must be copied over and over. ; The recursive approach builds up separate structures, and then ; concatanates them together. ; function ssl_read_ascii_create_struct, fieldnames, xData compile_opt idl2, hidden nFields = N_ELEMENTS(fieldnames) case (nFields) of 0: return, 0 ; this shouldn't happen ; Create a structure with a single field. 1: return, CREATE_STRUCT(fieldnames[0], TEMPORARY(*xData[0])) ; Create a structure with two fields. 2: return, CREATE_STRUCT( $ fieldnames[0], TEMPORARY(*xData[0]), $ fieldnames[1], TEMPORARY(*xData[1])) ; Create a structure with three fields. 3: return, CREATE_STRUCT( $ fieldnames[0], TEMPORARY(*xData[0]), $ fieldnames[1], TEMPORARY(*xData[1]), $ fieldnames[2], TEMPORARY(*xData[2])) else: begin ; Divide into 4 equal-sized sets of pointers, and concatanate ; the structures. Four is somewhat arbitrary, but on average, ; this will then require only 1/4 of the memory, as compared ; to simply concatanating the fields onto the end. d = nFields/4 ; Recursive call. return, CREATE_STRUCT( $ SSL_READ_ASCII_CREATE_STRUCT(fieldnames[0:d-1], xData[0:d-1]), $ SSL_READ_ASCII_CREATE_STRUCT(fieldnames[d:2*d-1], xData[d:2*d-1]), $ SSL_READ_ASCII_CREATE_STRUCT(fieldnames[2*d:3*d-1], xData[2*d:3*d-1]), $ SSL_READ_ASCII_CREATE_STRUCT(fieldnames[3*d:*], xData[3*d:*])) end endcase end ; ----------------------------------------------------------------------------- ; ; Purpose: The main routine. ; function read_ascii_cmdline, $ file, $ ; IN: RECORD_START=recordStart, $ ; IN: (opt) NUM_RECORDS=numRecords, $ ; IN: (opt) TEMPLATE=template, $ ; IN: (opt) START_LINE=dataStart, $ ; IN: (opt) FIELD_TYPES=fieldTypes, $ ; IN: (opt) FIELD_NAMES=fieldNames, $ ; IN: (opt) FIELD_LOCATIONS=fieldLocations, $ ; IN: (opt) FIELD_GROUPS=fieldGroups, $ ; IN: (opt) DELIMITER=delimiter, $ ; IN: (opt) MISSING_VALUE=missingValue, $ ; IN: (opt) COMMENT_SYMBOL=commentSymbol, $ ; IN: (opt) ; FIELDS=fields, $ ; IN: (opt) [not implemented] VERBOSE=verbose, $ ; IN: (opt) HEADER=header, $ ; OUT: (opt) COUNT=count ; OUT: (opt) COMPILE_OPT strictarr ;xxx ;later add a VERSION kw ? ; Set to return to caller on error. ; ON_ERROR, 2 ; Set some defaults. ; count = 0 currentVersion = 1.0 doVerbose = KEYWORD_SET(verbose) ; If no file specified, use DIALOG_PICKFILE ; if (n_elements(file) eq 0) then begin file = DIALOG_PICKFILE(/MUST_EXIST) if (file eq '') then RETURN, 0 endif ; check that the file is readable and appears to be ASCII ; ret = ssl_ra_check_file(file) case ret of -1: MESSAGE, 'File name must be a string.' -2: MESSAGE, 'File "' + file + '" not found.' -3: MESSAGE, 'Error Reading from file "' + file + '"' -4: MESSAGE, 'File "' + file + '" is not a valid ASCII file.' else: endcase ; Set which records to read. ; if (N_ELEMENTS(recordStart) ne 0) then recordStartUse = recordStart $ else recordStartUse = 0 if (N_ELEMENTS(numRecords) ne 0) then numRecordsUse = numRecords $ else numRecordsUse = 0 if (N_ELEMENTS(template) gt 0) then begin if (not ssl_ra_valid_template(template, message)) then $ MESSAGE, message versionUse = template.version dataStartUse = template.dataStart delimiterUse = STRING(template.delimiter) missingValueUse = template.missingValue commentSymbolUse = template.commentSymbol endif else begin versionUse = currentVersion dataStartUse = 0L delimiterUse = ' ' missingValueUse = !values.f_nan commentSymbolUse = '' endelse if n_elements(dataStart) ne 0 then $ dataStartUse = dataStart if n_elements(delimiter) ne 0 then $ delimiterUse = delimiter if n_elements(missingValue) ne 0 then $ missingValueUse = missingValue if n_elements(commentSymbol) ne 0 then $ commentSymbolUse = commentSymbol ; if fieldTypes keyword is set and it is not of type long, in otherwords it's a string ; array, then convert the string types of longs. if size(fieldTypes, /type) ne 3 and size(fieldTypes, /type) ne 0 then begin fieldTypesUse=make_array(n_elements(fieldTypes), /long) for i=0,n_elements(fieldTypes)-1 do begin case strlowcase(fieldTypes[i]) of 'int' : fieldTypesUse[i]=2L 'long' : fieldTypesUse[i]=3L 'float' : fieldTypesUse[i]=4L 'double' : fieldTypesUse[i]=5L 'string' : fieldTypesUse[i]=7L 'structure' : fieldTypesUse[i]=8L else: begin message, 'Invalid field_types entered.' message, 'Valid field_types include [int, long, float, double, string, structure].' return, -1 endelse endcase endfor endif else begin if size(fieldTypes, /type) eq 3 then fieldTypesUse = fieldTypes endelse if n_elements(dataStartUse) gt 1 then $ message, 'DATA_START must be a scalar.' if n_elements(byte(delimiterUse)) gt 1 then $ ; Might want to remove this message, 'DELIMITER must be one character.' ; restriction in the future. ; (For back version compatibility, we do not throw an error ; here if n_elements(missingValueUse) gt 1). ; ; The READ_ASCII that shipped with IDL 5.2.1 returns 0 when ; an array of comment symbols is specified. Set the error_state ; in this case, but, for back version compatibility, continue ; and reproduce the "return 0" behavior here. ; if n_elements(commentSymbolUse) gt 1 then begin message, $ 'Multiple comment symbols are not currently supported.', $ /noprint, $ /continue return, 0 endif if n_elements(template) gt 0 then begin fieldCountUse = template.fieldCount fieldTypesUse = template.fieldTypes fieldNamesUse = template.fieldNames fieldLocationsUse = template.fieldLocations fieldGroupsUse = template.fieldGroups endif else begin fieldCountUse = n_elements(fieldTypes) $ > n_elements(fieldNames) $ > n_elements(fieldLocations) $ > n_elements(fieldGroups) if fieldCountUse le 0 then $ fieldCountUse = ssl_ra_guess_columns( $ file, $ dataStartUse, $ commentSymbolUse, $ delimiterUse $ ) if size(fieldTypesUse, /type) eq 0 then fieldTypesUse = REPLICATE(4L, fieldCountUse) digits_str = strtrim(string(strlen(strtrim(string(fieldCountUse),2))),2) fstr = '(i' + digits_str + '.' + digits_str + ')' fieldNamesUse = 'field' + STRING(INDGEN(fieldCountUse)+1, format=fstr) fieldLocationsUse = LONARR(fieldCountUse) fieldGroupsUse = INDGEN(fieldCountUse) endelse ; if n_elements(fieldTypes) ne 0 then $ ; fieldTypesUse = fieldTypes if n_elements(fieldNames) ne 0 then $ fieldNamesUse = fieldNames if n_elements(fieldLocations) ne 0 then $ fieldLocationsUse = fieldLocations if n_elements(fieldGroups) ne 0 then $ fieldGroupsUse = fieldGroups ; Error check the field data. ; lengths = [ $ N_ELEMENTS(fieldTypesUse), $ N_ELEMENTS(fieldNamesUse), $ N_ELEMENTS(fieldLocationsUse), $ N_ELEMENTS(fieldGroupsUse) $ ] if (TOTAL(ABS(lengths - SHIFT(lengths, 1))) ne 0) then $ MESSAGE, 'Field data (types/names/locs/groups) not the same length.' ; Set the template to use. ; templateUse = { $ version: versionUse, $ dataStart: dataStartUse, $ delimiter: BYTE(delimiterUse), $ missingValue: missingValueUse, $ commentSymbol: commentSymbolUse, $ fieldCount: fieldCountUse, $ fieldTypes: fieldTypesUse, $ fieldNames: fieldNamesUse, $ fieldLocations: fieldLocationsUse, $ fieldGroups: fieldGroupsUse $ } ; Print verbose information. ; if (doVerbose) then begin PRINT, 'Using the following file attributes ...', FORMAT='(/A)' PRINT, ' Data Start: ' + STRTRIM(STRING(dataStartUse), 2) PRINT, ' Delimiter: ' + $ STRTRIM(STRING(FIX(BYTE(delimiterUse))), 2) + 'B' PRINT, ' Missing Value: ' + STRTRIM(STRING(missingValueUse), 2) PRINT, ' Comment Symbol: ' + commentSymbolUse PRINT, ' Field Counts: ' + ssl_ra_stringit(fieldCountUse) PRINT, ' Field Types : ' + ssl_ra_stringit(fieldTypesUse) PRINT, ' Field Names : ' + ssl_ra_stringit(fieldNamesUse) PRINT, ' Field Locs : ' + ssl_ra_stringit(fieldLocationsUse) PRINT, ' Field Groups: ' + ssl_ra_stringit(fieldGroupsUse) PRINT, ' Template Version: ' + STRTRIM(STRING(versionUse), 2) PRINT endif ; Try to read the file. ; pData = ssl_ra_read_from_templ(file, templateUse, recordStartUse, $ numRecordsUse, doVerbose, numFieldsRead, FieldNames, count, num_blocks, header=header) ; Return zero if no records read. ; if (count eq 0) then $ RETURN, 0 ; Concatenate the blocks into fields. ; xData = ptrarr(numFieldsRead) ; If an error occurs, free up our temp pointers. CATCH, err if (err ne 0) then begin CATCH, /CANCEL PTR_FREE, xData, pData MESSAGE, /REISSUE_LAST return, 0 endif for f=0L, numFieldsRead-1 do begin type = SIZE(*pData[f,0], /TYPE) dims = SIZE(*pData[f,0], /DIMENSIONS) n_dims = SIZE(*pData[f,0], /N_DIMENSIONS) if (count eq 1) then begin ; if the file contains a single record, it is really ; two-dimensional: n fields x 1 record n_dims = 2 dims = lonarr(2) dims[0] = SIZE(*pData[f,0],/N_ELEMENTS) endif dims[n_dims-1] = count xData[f] = ptr_new(make_array(DIMENSION=dims, TYPE=type)) start=0L for b=0L, num_blocks-1 do begin sz = SIZE(*pData[f,b],/N_ELEMENTS) stop = start + sz - 1 (*xData[f])[start:stop] = *pData[f,b] ptr_free, pData[f,b] start = stop + 1 endfor endfor ; Put the fields into a structure. data = SSL_READ_ASCII_CREATE_STRUCT(fieldnames, xData) ; Clean up the heap data. ; for f = 0L, numFieldsRead-1 do $ PTR_FREE, xData[f] ; Print verbose information. ; if (doVerbose) then begin PRINT, 'Output data ...' HELP, data, /STRUCTURES PRINT endif ; Return the structure. ; RETURN, data end ; read_ascii