; $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