; Copyright (c) 1998-20051 A.P. Hitchcock  All rights reserved
;+
;NAME:
;		PARSE_SDF
;
;LAST CHANGED: ----------------------------------- 09-Dec-05 (aph)
;
; PURPOSE:
;	This function generates a structure from a string
; If the string contains multiple structures it only returns the first structure.
; It also trims the input string to all past the extracted structure.
; Where necessary, in partuclar inarrays which contain structures, named structures are used.
;
; CATEGORY:
;	utility: string manipulation
;
; CONTENTS:
;	MAKE_STAR	function to make named array from anonymous array
;	PARSE_SDF	main routine
;
; CALLING SEQUENCE:
;	Result = PARSE_SDF(dummy, struct=struct, verbose = verbose)
;
; INPUTS:
;	DUMMY	a string containing structure contents
;	EXAMPLES: '{ Name = "Time"; Unit = "sec";};'
;			 '{ Name = "Energy"; Min = 285; Max = 322; Points = (3, 285, 286, 287);};'
;			 '{ Name = "Time"; Unit = "sec";}; embed_str = {name = "new";int = 5;};};'
;
; KEYWORDS:
;	STRUCT	pre-defined named structure to act as template
;	VERBOSE	print a blow-by-blow descrition (you were warned !!)
;
; OUTPUTS: anonymous structure which may contain named structures
;      (Tag_Name1 : Tag_Definition1,..., Tag_Namen : Tag_Definitionn}
;
; COMMON BLOCKS: sdformat
;
; PROCEDURE: The structure is first tested to ensure it starts with {
; It is then read by removing each tagname and using analysis of the following
; string parts to identify one of 7 possible structure components
;
;				Identify by testing		Isolate	by 		Convert
; Component  	first char	(____;)		extracting		by using
;  ---------   ----------------------  -------------   ---------
; STRUCTURE		{						whole string	parse_sdf			**
; Array			(						next ')'		type specific
; ----- after removing (##, ..... identify and convert all ___,
;   of STRUCT	{						whole string	parse_sdf			**
;   of String	"						sep_parts		none				**
;   of Real/Integer			'.'			sep_parts		float				**
;
; String		"			'"'			to next ';'		none				**
; Real						'.'			to next ';'		float				**
; Integer					(all other)	to next ';'		fix					**
; ------------------------------------------------------------
;
; BUGS
; 	Does not work to handle arrays of structures
; need to find how to make named structure from strings
;
; MODIFICATION HISTORY:
; (10-feb-01 aph) first version
; (11-feb-01 aph) change from bl5 to sdf (self-defined format); make_star
; (22-feb-01 aph) residual syntax pasring added - Booleans & arrays of structures of structures
; (17-aug-01 aph) tinkered but no mods getting Aug-01 dataformats read
; (12-may-02 aph) correct header name format
; (12-mar-03 aph) change '0','1','2' to 'a0'.. in named array structs
;      "          and pt in kluge to replace 'END' tags, for IDL5.6 compatibility
; (09-dec-05 aph) tracking added for troubleshooting new SDF file structures
;-

function make_star, anon_struct, s_name
;
; generates a named structure from an anonymous structure
; NB create_struct requires EACH field value to be listed individually
; so cannot use an array variable - ARGHH !!!
;

t = anon_struct
t_names = tag_names(t)
; print, 'making named structure ', s_name  & help,t,/struct
; generate named structure from elements
 CASE n_tags(t) OF
	1: s = create_struct(name = s_name, t_names, t.(0))
	2: s = create_struct(name = s_name, t_names, t.(0),t.(1))
	3: s = create_struct(name = s_name, t_names, t.(0),t.(1),t.(2))
	4: s = create_struct(name = s_name, t_names, t.(0),t.(1),t.(2),t.(3))
	5: s = create_struct(name = s_name, t_names, t.(0),t.(1),t.(2),t.(3),t.(4))
	6: s = create_struct(name = s_name, t_names, t.(0),t.(1),t.(2),t.(3),t.(4),t.(5))
	7: s = create_struct(name = s_name, t_names, t.(0),t.(1),t.(2),t.(3),t.(4),t.(5),t.(6))
	8: s = create_struct(name = s_name, t_names, t.(0),t.(1),t.(2),t.(3),t.(4),t.(5),t.(6),t.(7))
	9: s = create_struct(name = s_name, t_names, t.(0),t.(1),t.(2),t.(3),t.(4),t.(5),t.(6),t.(7),t.(8))
	10: s = create_struct(name = s_name, t_names, t.(0),t.(1),t.(2),t.(3),t.(4),t.(5),t.(6),t.(7),t.(8),t.(9))
	11: s = create_struct(name = s_name, t_names, t.(0),t.(1),t.(2),t.(3),t.(4),t.(5), $
	       t.(6),t.(7),t.(8),t.(9),t.(10))
	12: s = create_struct(name = s_name, t_names, t.(0),t.(1),t.(2),t.(3),t.(4),t.(5), $
	       t.(6),t.(7),t.(8),t.(9),t.(10),t.(11))
	13: s = create_struct(name = s_name, t_names, t.(0),t.(1),t.(2),t.(3),t.(4),t.(5), $
	       t.(6),t.(7),t.(8),t.(9),t.(10),t.(11),t.(12))
	14: s = create_struct(name = s_name, t_names, t.(0),t.(1),t.(2),t.(3),t.(4),t.(5), $
	       t.(6),t.(7),t.(8),t.(9),t.(10),t.(11),t.(12),t.(13))
	15: s = create_struct(name = s_name, t_names, t.(0),t.(1),t.(2),t.(3),t.(4),t.(5), $
	       t.(6),t.(7),t.(8),t.(9),t.(10),t.(11),t.(12),t.(13),t.(14))
	16: s = create_struct(name = s_name, t_names, t.(0),t.(1),t.(2),t.(3),t.(4),t.(5), $
	       t.(6),t.(7),t.(8),t.(9),t.(10),t.(11),t.(12),t.(13),t.(14),t.(15))
	17: s = create_struct(name = s_name, t_names, t.(0),t.(1),t.(2),t.(3),t.(4),t.(5), $
	       t.(6),t.(7),t.(8),t.(9),t.(10),t.(11),t.(12),t.(13),t.(14),t.(15),t.(16))
	18: s = create_struct(name = s_name, t_names, t.(0),t.(1),t.(2),t.(3),t.(4),t.(5), $
	       t.(6),t.(7),t.(8),t.(9),t.(10),t.(11),t.(12),t.(13),t.(14),t.(15),t.(16),t.(17))
	19: s = create_struct(name = s_name, t_names, t.(0),t.(1),t.(2),t.(3),t.(4),t.(5), $
	       t.(6),t.(7),t.(8),t.(9),t.(10),t.(11),t.(12),t.(13),t.(14),t.(15),t.(16),t.(17),t.(18))
	20: s = create_struct(name = s_name, t_names, t.(0),t.(1),t.(2),t.(3),t.(4),t.(5), $
	       t.(6),t.(7),t.(8),t.(9),t.(10),t.(11),t.(12),t.(13),t.(14),t.(15),t.(16),t.(17),t.(18),t.(19))
ELSE : BEGIN
	print, 'make_star: only set up for 20 elements maximum'
ENDELSE
ENDCASE

return, s

end


; ********************************************************************************
; --------------------------------------------------------------------------------
; ********************************************************************************

function parse_sdf, dummy, verbose = verbose

common sdformat, starnum	; starnum is last used number of structure arrays

str = dummy
st = 0
; ------------- check input is a string
t = size(str)
if t(1) NE 7 and strmid(str,0,1) NE '{' then begin
	print, ' parse_sdf: Not a structure string'
	return, st
endif
if keyword_set(verbose) then  print, ' parse_sdf is analysing ....', str
str = strtrim(strmid(str,1),2)		; remove opening '{'

s = {dummy:' '}		; anonymous structure is default

loop = 0
while  strmid(str,0,1) NE ';' AND strmid(str,0,1) NE '}' do begin
if strlen(str) EQ 0 then goto, BAIL
loop = loop + 1
if loop GT 500 then begin
	print, 'parse_sdf terminated by max number of entries (500)'
	goto, BAIL
endif

; remove and save name of the next structure component
	equal = strpos(str, '=')
	name = strtrim(strmid(str,0,equal),2)
	if keyword_set(verbose) then print, ' ', name
	str = strtrim(strmid(str,equal+1),2)

; -------- check first character -------------------------------
	first = strtrim(strmid(str,0,1),2)
	CASE first of
	'{': BEGIN
		if name EQ 'END' then name = 'kluge'        ; kluge to defeat IDL5.6 penchant for adding ' END' to strings
		s =  create_struct(s, name, parse_sdf(str))
	  END
	'"': BEGIN
		parts = str_sep(str,'"')
	 	s =  create_struct(s, name, parts(1))
	 	str = strtrim(strmid(str,strpos(str,';')+1),2)
	  END
	'(': BEGIN
		arr_size = fix(strmid(str,1,strpos(str,',')))
		if keyword_set(verbose)then print, 'array: ', name, '      size:', arr_size
		str = strtrim(strmid(str, strpos(str,',')+1),2)
;	-------- array of structures - NB all structures MUST have same NAME, number and types of parts
		array_first = strtrim(strmid(str,0,1),2)        ; check first character of contents
		CASE array_first of
		'{': BEGIN
; -- handle multi-element arrays of structures
		   	starnum = starnum + 1	; increment for next array of structures
; ---------- check for array of structures, containing structures
			test = strpos(str, '};})')
			if test GT 0 then begin
;				print, 'Nested structures in this array - GULP !'
				tmp_str = str
				str = strtrim(strmid(str,0,test+3),2)
				; find the total number of structures
				n_struct = 0 & tstr=str
				while strpos(tstr, '};') GT 0 do begin
					n_struct=n_struct+1
					tstr = strmid(tstr,2+strpos(tstr, '};'))
				endwhile
;				print, 'total # of nested structures ', n_struct
				starnum = starnum + 1	; increment for next array of structures
				t0=parse_sdf(str) ; & help,t0,/struct	; get first structure for format
; ------------- convert internal anonymous structures to named structures
				starint = starnum + 1	; increment for internal structures
				star_int = 'star' + strtrim(string(starnum),2)
				t = make_star(t0.(0),star_int)	; make the root named structure
				inar = replicate(t,n_tags(t0))
				for iii = 1, n_tags(t0)-1 do begin
					inar[iii] = make_star(t0.(iii),star_int) ; & help, inar,/struct
				endfor
				CASE fix(n_struct/arr_size) OF
					1: i_str = create_struct(name = star, ['a0'], inar[0])
					2: i_str = create_struct(name = star, ['a0','a1'], inar[0],inar[1])
					3: i_str = create_struct(name = star, ['a0''a1','a2'], inar[0],inar[1],inar[2])
				ELSE: print, 'More than 3 structures in an array of structures with structures'
				ENDCASE
; ------------- get subsequent structure and convert to named structure
				arrstr = replicate(i_str,arr_size)
				for iv = 1, arr_size-1 do begin
					t0=parse_sdf(str) ; & help,t0,/struct	; get next structure
					t = make_star(t0.(0),star_int)	; make the root named structure
					inar = replicate(t,n_tags(t0))
					arrstr[iv].(0) = inar[0]
					for iii = 1, n_tags(t0)-1 do begin
						inar[iii] = make_star(t0.(iii),star_int) ;	& help, inar,/struct
						arrstr[iv].(iii) = inar[iii]
					endfor
			; put the internal structures into the array of structures
				endfor

	;strip out all of nested structures section
				str = strtrim(strmid(tmp_str,test+2),2)
			endif else begin
; --------- process non-nested arrays of structures
				t0=parse_sdf(str) ; & help,t0,/struct	; get first structure for format
			; ----- create a named structure with a unique name ------
				star = 'star' + strtrim(string(starnum),2)
				t = make_star(t0,star)	; make the root named structure
				arrstr = replicate(t,arr_size)
	;			print, name, ' element 0' & help, arrstr[0],/struct
				for ii = 1, arr_size-1 do begin
					t = parse_sdf(str)
	;				print, name, ' element ',ii
					for i = 0,n_tags(t)-1 do arrstr[ii].(i) = t.(i)
	;				help, arrstr[ii],/struct
				endfor
			endelse
		END
		'"': BEGIN				; array of STRINGS
			arrstr = strarr(arr_size)
			arr = strmid(str,0,strpos(str,')'))
			for i = 0, arr_size-1 do begin
				next = strmid(str,0,strpos(str,','))
				parts = str_sep(next,'"')
				arrstr(i) = parts(1)
			endfor
			str = strmid(str,strpos(str,','+1))
		END
		ELSE: BEGIN
		    arrstr=fltarr(arr_size)				; array of numbers (REALS)
		    next = strmid(str,0,strpos(str,')'))
		    arrstr = str_sep(next,',')
		ENDELSE
 		ENDCASE
		s =  create_struct(s, name, arrstr)
		if keyword_set(verbose) then help, s,/struct
		str = strtrim(strmid(str,strpos(str,';')+1),2)
	  END
	ELSE: BEGIN
; ------- check contents for boolean, real or integer -----
		next = strtrim(strmid(str,0,strpos(str,';')),2)
		CASE strlowcase(next) of
			'true':  s =  create_struct(s, name, 0)
			'false': s =  create_struct(s, name, 1)
			ELSE: BEGIN
		;		print, next
		;		if strpos(next,'.') GE 0 then begin			; identify a real by '.'
					s =  create_struct(s, name, float(next))
		;		endif else s =  create_struct(s, name, fix(next))
			ENDELSE
		ENDCASE
		str = strtrim(strmid(str,strpos(str,';')+1),2)
	ENDELSE
	ENDCASE
ENDWHILE
BAIL:
if strmid(str,0,1) EQ ')' then str = strmid(str,1)	; shift if ending a structure
dummy = strtrim(strmid(str,2),2)		; remove terminating ';' or ',' for arrays
; remove first 'dummy' tag needed to bootstrap structure generation
tn = tag_names(s)
t = create_struct(tn(1), s.(1))
for i = 2, n_tags(s)-1 do t = create_struct(t,tn(i),s.(i))
s = t

return, s
end
