# bullform - Creates the result file ready for external use:
# <TD> lines removed after setting properly the directory informations on their lines
# by Bernard Nivelet
#
# perl bullform.pl result.csv trace >result_in_form.csv

$now_string = localtime();
print "$now_string\n\n";

 $version = "1.0";
 print "bullform $version\n";

 my $result = shift;
 $trace = shift;

# globals:
 $call_limit = 20;		# wall against too much recurcive calls
 $crlf = "
";
 $comma = ",";
 $Directory="<Directory>";
 $File = "<File>";
 $i = 0;
 $j = 0;
 $k = 0;
 $length_root_name = 0;
 $nb_call=0;
 $pointer_in = 0;
 $result_in_form = "";
 $result_as_it;
 $sub;
 $eight_commas = ",,,,,,,,";
 $ten_commas = $eight_commas . ",";
 $Fourty_Five_commas = $ten_commas . $ten_commas . $ten_commas . $ten_commas . ",,,,,";
 $eight_semicolumns = ";;;;;;;;";
 $ten_semi_columns = $eight_semicolumns . ";";
 $Fourty_Five_semi_columns = $ten_semi_columns .  $ten_semi_columns .  $ten_semi_columns .  $ten_semi_columns . ";;;;;";
 $root_name = "";
 $TD="<TD>";
 $two_commas = ",,";
 $two_semi_columns = ";;";
 $v_comma = 44;
 $v_cr = 13;		# carriage-return ASCII value
 $v_lf = 10;		# line-feed ASCII value
 $v_semicolumn = 59;
 $v_slash = 47;

 my $size = 0;
		
	open(IF, $result) || die "can't read $result";
		$size = (stat($result))[7];
						if ( $trace==1 ){
							print "Size of $result = $size\n";
						}
		binmode OF;	# For Windows, read file as bytes.
		read IF, $result_as_it, $size;
	close(IF);

	$i = 1;				# loop searching for first column skipper : "," or ";"
	$j= 0 ;
	while( $i == 1){
		$k =  vec($result_as_it, $j, 8);
		if( $k == $v_comma ){		# if "," found then "," is the column skipper
			$i = 0;
						if ( $trace==1 ){
							print "Case where CSV separator is ',' rather than ';'\n";
						}

		}elsif ( $k == $v_semicolumn ){		# if ";" found then ";" is the column skipper
			$two_commas = $two_semi_columns;
			$eight_commas = $eight_semicolumns;
			$ten_commas = $ten_semi_columns;
		 	$Fourty_Five_commas =  $Fourty_Five_semi_columns;
			$v_comma = $v_semicolumn;
			$i = 0;
						if ( $trace==1 ){
							print "Case where CSV separator is ';' rather than ','\n";
						}

		}else{
			$j += 1;		# continue scanning
		}
	}
 
	$pointer_in = index($result_as_it, $Directory, 0 );
	$pointer_in = line_beginning( $pointer_in );
	$i=$pointer_in;
	while( vec( $result_as_it, $i , 8 ) != $v_comma ){
		$i +=1;
	}
	$j=$i;					# $i is on last character before comma
	while( vec( $result_as_it, $j , 8 ) != $v_slash ){
		$j -= 1;
	}
	$length_root_name = $j - $pointer_in + 1;	# $j is on first character after slash
	$root_name .= substr($result_as_it, $pointer_in, $length_root_name);

	$result_in_form .= substr( $result_as_it, 0 , $pointer_in - 1 );

						if ( $trace==1 ){
							$i = index($result_as_it, $Directory, $pointer_in );
						$sub = substr( $result_as_it, $pointer_in , $i+11 - $pointer_in + 1 );	
							print "Current line when entering checkdir the first time:\n";			
							print "                  $sub\n";
						}

	$i=&checkdir( 0 );
	if($i<0){
		print "Program stopped because of recursivity problem: -$i calls of checkdir\n";
	}
	$r = $size - $pointer_in + 1;
	$result_in_form .= substr( $result_as_it, $pointer_in , $r );		# adding the end of $result_as_it at the end of $result_in_form
	print "\n\n                  Output of ($result)_in_form\n\n";
	print "$result_in_form";

 exit(0);

# ================================================================
# recursive routine to process directories
# &checkdir( $level );

sub checkdir {
 my $level= shift;

 my $case = 0;
 my $continue_for_embedded_directories = 0;
 my $fl = 0;
 my $p = 0;
 my $pc = 0;
 my $pd = 0;
 my $pf = 0;
 my $pt = 0;
 my $i_insert_dir_size = 0;
 my $q = 0;
 my $r = 0;
 my $sub_entry;
 my $test_fullfilled = 0;
 my $TD_line_out = 0;

 $nb_call+=1;		# testing wall against too much recurcive calls
 if(nb_call>$call_limit){
	print $nb_call "th call: exit!\n";
	return -$nb_call;
 }


 $p = index($result_as_it, $Directory, $pointer_in );

						if ( $trace==1 ){
							$r=1;
							$pc = $p - $pointer_in + 11;
							$sub_entry = substr( $result_as_it, $pointer_in , $pc );
							print "checkdir level $level: Entering checkdir for $sub_entry\n";
						}
 
 $r = index($result_as_it, $File, $pointer_in );
 $q = index($result_as_it, $TD, $pointer_in ); 
 if( ($p<$q) && ($p<$r) ){				# First <Directory> line found to be processed
						# start looking for directory name longer than number of characters available before <Directory>
	my $nv = 0;
	my $sv = "";
	$i = line_beginning($p);
	$j = $i - $pointer_in;
	if( $j>0 ){
		$result_in_form .= substr( $result_as_it, $pointer_in , $j  );
		$pointer_in += $j;
	}
	while(  vec($result_as_it, $pointer_in, 8) == $v_comma ) {
		$nv += 1;
		$sv .= $comma;
		$pointer_in += 1;
	}
	$result_in_form .= $sv;
	$k = index($result_as_it, $comma, $pointer_in);
	$pointer_in = index( $result_as_it,  $root_name, $pointer_in) +  $length_root_name;
	$j = $k - $pointer_in;
	if( ($j + $nv*3) > 48 ){
		$result_in_form .= substr( $result_as_it, $pointer_in , $j  ) . $crlf . $eight_commas;
		$pointer_in = $p;
	}						# end looking for directory name longer than number of characters available before <Directory>
	$result_in_form .= substr( $result_as_it, $pointer_in , $p + 11  - $pointer_in  ) . $two_commas;
	$i_insert_dir_size = length($result_in_form);	# $i_insert_dir_size is the pointer in $result_in_form where to paste later the directory sizes
	$pointer_in = line_ending( $p );	# after the first crlf in entry
	$result_in_form .= $Fourty_Five_commas . $crlf;

						if ( $trace==1 ){
							print "checkdir level $level: Here, processing of first <Directory> line ";
							print "is finished\n";
						}

	$case = 1;					# Start of evaluation of the situation after first line	
	$fl = 0;
	$pc = $pointer_in;
	while ( $case>0 ){
		$r = index($result_as_it, $File, $pc );		# $r is < 0 if no more File line (end of file)
		$p = index($result_as_it, $Directory, $pc);	# $p is < 0 if no more Directory line (end of file)
 		$q = index($result_as_it, $TD, $pc);
		$test_fullfilled = 0;
		if( $r>0 ){
			if( $r < $q ){			# If a File line is found, it is necessarily followed by a TD line, so $q is > 0
				if( ($r<$p) || ($p<0) ){	# $p<0 means end of file
					$pc = $r + 6;
					$pf = $pc;
					$fl += 1;	# File line found before DIRECTORY line (or end of file) and TD line thus following current DIRECTORY line 
					$test_fullfilled = 1;

						if ( $trace==1 ){
							$q = line_beginning( $r );
							$sub = substr( $result_as_it, $q , $r+6-$q+1 );
							print "checkdir level $level: Here $fl <File> line have been found:  $sub\n";
						}

				}
			}
		}
		if( ($p>0) && ($test_fullfilled == 0) ){
			if( ($p < $r) && ($p < $q) ){	# If a DIRECTORY line is found, it is necessarily followed by File and TD lines, so $q and $r are > 0
				$pc = $p;
				$pd = $p;
				$case = -1;	# DIRECTORY line found (before TD line) thus following entry DIRECTORY line and eventual File lines
				$test_fullfilled = 2;

						if ( $trace==1 ){
							$q = line_beginning( $p );
							$sub = substr( $result_as_it, $q , $p+11-$q+1 );
							print "checkdir level $level: Here <Directory> line found:  $sub\n";
						}
			}
		}
		if( ($q>0) && ($test_fullfilled == 0) ){
							# If a TD line is found, could be at the end of file, in which case $r or $p are < 0
			if( ( ($q<$r) || ($r<0) ) && ( ($q<$p) || ($p<0) ) ){
				$pc = $q;
				$pt = $q;
				$case = 0;	# TD line found (with no DIRECTORY line before) so following current  File lines
				$test_fullfilled = 3;

						if ( $trace==1 ){
							$p = line_beginning( $q );
							$i = line_ending($p);
							$sub = substr( $result_as_it, $p , $i-2-$p+1 );
							print "checkdir level $level: Here <TD> line found:  $sub\n";
						}

			}
		}
		if( $test_fullfilled == 0 ){
			$case = -2;
		}
	}	# end while $case>0
						if ( $trace==1 ){
							print "checkdir level $level: Here evaluation of situation after ";
							print "directory line is done\n";
						}

	if( $fl > 0 ){					# File lines to copy
		$q =  line_ending( $pf );
		$result_in_form .= substr( $result_as_it, $pointer_in , $q - 1 - $pointer_in + 1 );
		$pointer_in = $q;
						if ( $trace==1 ){
							print "checkdir level $level: Here, last of $fl file lines have been copied\n";
						}
	}

	$TD_line_out = 0;
	if( $case == -1 ){				# embedded directories to process
		if( $fl > 0 ){
			$result_in_form .= $crlf;
		}
		$continue_for_embedded_directories = 1;
		$pointer_in = line_beginning( $pd );
		$p = index($result_as_it, $Directory, $pointer_in);
		while( $continue_for_embedded_directories == 1){

						if ( $trace==1 ){
							$sub = substr( $result_as_it, $pointer_in , $p+10-$pointer_in+1 );
							print "checkdir level $level: Here recursive call of checkdir at $sub\n";
						}

			$p = &checkdir( $level+1 );			# On return, $pointer_in is at the beginning of the next line
			if($p<0){
				return $p;		# In case too much recursive calls: stop
			}
			$r = 1;
			$pc = $pointer_in;
			$fl = 0;
			while( $r>0 ){		# looking for File lines between Directories
 				$r = index($result_as_it, $File, $pc );
 				$p = index($result_as_it, $Directory, $pc );
 				$q = index($result_as_it, $TD, $pc );
				if( $r>0 && ($r<$p || $p<0) && ($r<$q || $q<0)){
					$fl +=1;
					$pc = $r+6;
				}else{
					$r = 0;
				}
			}
			if( $fl > 0 ){					# File lines to copy
				$q =  line_ending( $pc );
				$result_in_form .= substr( $result_as_it, $pointer_in , $q - 1 - $pointer_in + 1 );
				$pointer_in = $q;
						if ( $trace==1 ){
							print "checkdir level $level: Here, last of $fl file lines have been copied\n";
						}
				}
 			$p = index($result_as_it, $Directory, $pointer_in );
 			$q = index($result_as_it, $TD, $pointer_in );
			if( ($q<$p) || ($p<0) ){							# TD line found to be  processed 
				$continue_for_embedded_directories = 0;
				$pc = $pointer_in;
				$TD_line_out = 1;
			}
		} # while $continue_for_embedded_directories

						if ( $trace==1 ){
							print "checkdir level $level: Here processing of embedded directory is finished\n";
						}						

	}

	if( ($case == 0) ||  ($TD_line_out == 1) ){						# Processing of TD line
		$p = index($result_as_it, $ten_commas, $pc ) + 10;
		$pointer_in =  line_ending( $p );
		$r = $pointer_in - 2 - $p + 1;
		substr( $result_in_form, $i_insert_dir_size , $r ) = substr( $result_as_it, $p , $r );	# directory counters are copied

						if ( $trace==1 ){
							print "checkdir level $level: Here TD line have been processed\n";
						}

	 }else{
		# nothing to do
	}  	# ($case == 0) ||  ($TD_line_out == 1)
		
						if ( $trace==1 ){
							print "checkdir level $level: Exit $sub_entry\n";
						}

 }elsif( ($q<$p) && ($q<$r) ){
		# <TD> line impossible: nothing to do
 }else{
		# <File> line impossible: nothing to do
 }
 return $nb_call;
}	#checkdir

# ================================================================

# returns the pointer on the beginning of the line containing the pointer $current
sub line_beginning{
	my $current = shift;
	my $r = 1;

	while( $r==1 ){
		if( vec( $result_as_it, $current , 8 ) == $v_lf ){
			$r = 0;
		}else{
			$current -= 1;
		}
	}
	$current += 1;
	return $current;

}

# ================================================================

# returns the pointer on the end of the line following the pointer $current
sub line_ending{
	my $current = shift;
	my $r = 1;

	while( $r==1 ){
		if( vec( $result_as_it, $current , 8 ) == $v_lf ){
			$r = 0;
		}else{
			$current += 1;
		}
	}
	$current += 1;
	return $current;

}

# ================================================================