#[BOFN]###############################################################################
#
#
#Pagenews - a free script to publish news on websites
#Copyright (C) 2004,2005,2006,2007,2008 Philipp Kindt
#
#This file is part of Pagenews.
#
# 	 This program is free software: you can redistribute it and/or modify
#    it under the terms of the GNU General Public License as published by
#    the Free Software Foundation, either version 3 of the License, or
#    (at your option) any later version.
#
#    This program is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU General Public License for more details.
#
#    You should have received a copy of the GNU General Public License
#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
#[EOFN]###############################################################################


#This is the don philippe key value pair parser
#it is the interface for three-dimensional key-value fields,
#that means simple key-value pairs (like in perl hashes), built together to blocks.
#
#the kv-pairs describe the properties of a block,
#a block can be seen as a struct in C
#
#there are some reserved things:
#-a Pipe ("|") must not be inside a kv-pair as it separates a key from a value
#the key "DKVP_BID" must not be occupied as it separates the blocks and contains the block id   
#
#the file has a header separated by an empty line which are 2 NEWLINEs (\n\n)
#a header is interpreted again as a block - make sure there is no newline in the header!
package donkvparser;
use donstdlib;
########################################################################################################
#constructor
sub new{
	my $classname = shift();
	my $self = {};
	bless($self,$classname);
	return $self;
};

########################################################################################################
#this reads and parses the file (using parse()) $_[1]
sub read{
	my $self = shift();
 	my $fname = shift();
 	$self->{'filename'} = $fname;
 	if(donstdlib::make_sure_that_exists($fname,"CHECK") != 1){
 		donstdlib::error("donstdlib_filedoesnotexist",$fname);
 	}
 	my $ftxt = donstdlib::read_file($fname);
	
	#split the keys and values up into single parts
	if($ftxt =~ m/\n\n/){	
		(my $fheader, $ftxt) = split(/\n\n/,$ftxt);
			
		$self->parse($fheader,"header");			#parse the header and store it in the class as "header"
		$self->parse($ftxt, "data");				#parse the actual file and store it as data
	}else{
			#work - around against problems with empty data blocks and empty files
		$\ = "\n";	
		chomp($ftxt);

		$self->parse($ftxt,"header");				#parse the file and store it in the class as "header"		
#		$self->parse($ftxt,"data");				#parse the file and store it in the class as "data"		
		$self->{'info'}->{'data'}->{'nrofblocks'} = 0;
	};
};


########################################################################################################
#this parses the text in $_[1] and places it in the object hash reference key given by $_[2] 
#private function!
sub parse{
	my $self = shift();
	my $ptxt = shift();					#the text to parse
	my $hashelement = shift();			#the hash element to store the parsed data as an array
	my @parts = split(/\|/,$ptxt);
	
	#place into memory structure
	my $blockcnt = -1;
	
	for (my $partcnt = 0; $partcnt < @parts; $partcnt = $partcnt + 2){
		
		#check for next block
		if($parts[$partcnt] eq "DKVP_BID"){
			$blockcnt++;
		};
		if($blockcnt >= 0){
			$self->{$hashelement}->[$blockcnt]->{$parts[$partcnt]} = $parts[$partcnt + 1];
			
		};
	};
	$self->{'info'}->{$hashelement}->{'nrofblocks'} = $blockcnt + 1;
};
########################################################################################################
#returns a copy of the block having index $_[1]
#whichpart in $_[2]
sub get_block_by_index{
	my $self = $_[0];
	return $self->{$self->determine_whichpart($_[2])}->[$_[1]]; 
}
########################################################################################################
#sets block (=hash) in $_[2] having index $_[1]
#whichpart in $_[3]
#the BID must be set in the block hash!
#returns 0 if succeeded, -1 in case of failure
sub set_block_by_index{
	my $self = $_[0];
	if($_[2]->{'DKVP_BID'} ne ""){
		$self->{$self->determine_whichpart($_[3])}->[$_[1]] = $_[2]; 
		return 0;	
	}else{
		return -1;
	}
}

########################################################################################################
#returns a copy of the block having ID $_[1]
#whichpart in $_[2]
sub get_block{
	my $self = $_[0];
	my $whichpart = $self->determine_whichpart($_[2]);
	return $self->{$whichpart}->[$self->get_index($_[1],$whichpart)];
}

########################################################################################################
#this function returns the arrayindex of blockid $_[1]
#$_[2] can be "data" for data part (footer) or "header" for header. 
#When empty (or anything different from "header"), it will deliver footer data
#causes an error message when not found
#if $_[3] is an non-empty string, instead of the block id the key $_[3] is compared.
#if $_[4] is non empty, instead of an error message the index -1 is returned
sub get_index{
	my $self = $_[0];
	my $whichpart = $_[2];
	my $key;
	if($_[3] eq ""){
		$key = 'DKVP_BID';
	}else{
		$key = $_[3];		
	};
	if($whichpart ne "header"){
		$whichpart = "data";
	}else{
		$whichpart = "header";
	};
	for(my $cnt = 0; $cnt < @{$self->{$whichpart}}; $cnt++){
		if($self->{$whichpart}->[$cnt]->{$key}  eq $_[1]){			
			return $cnt;
		};
	};
	if($_[4] eq ""){	
		donstdlib::error("dkvp_blockidnotfound",$_[1]);
	}else{
		return -1;
	};
};

########################################################################################################
#this (private) function causes an error message if block having the id $_[1] does not exist
#$_[2] can be "data" for data part (footer) or "header" for header. 
#When empty (or anything different from "header"), it will deliver footer data

sub make_sure_that_exists{
	my $self = $_[0];
	my $whichpart = $_[2];
	if($whichpart ne "header"){
		$whichpart = "data";
	}else{
		$whichpart = "header";
	};

	if(!(defined($self->{$whichpart}->[$self->get_index($_[1])]))){
		donstdlib::error("dkvp_blockidnotfound",$_[1]);
	};
};
########################################################################################################
############################Functions for Data manipulation#############################################
########################################################################################################
#this function determines weather header or data is meaned.
#$_[1] can be "data" for data part (footer) or "header" for header. 
#When empty (or anything different from "header"), footer data will be used
sub determine_whichpart{
	my $whichpart = $_[1];
	if($whichpart ne "header"){
		$whichpart = "data";
	}else{
		$whichpart = "header";
	};
	return $whichpart;
};

########################################################################################################
#This function gets the data of blockid $_[1] and hash key $_[2]. 
#$_[3] can be "data" for data part (footer) or "header" for header. 
#When empty (or anything different from "header"), it will deliver footer data
sub get_value{
	my $self = $_[0];
	my $whichpart = $self->determine_whichpart($_[3]);
	
	return $self->{$whichpart}->[$self->get_index($_[1],$whichpart)]->{$_[2]};
};

########################################################################################################
#this function modiefies the value of key $_[2] of blockid $_[1].
#it writes value of $_[3] in memory. 
#if the key $_[2] does not exist, it will be added
#if the block $_[1] does not exist, it won't be added but an error msg caused!
#$_[4] can be "data" for data part (footer) or "header" for header. 
#When empty (or anything different from "header"), it be understood as footer data

#if the value $_[3] contains any pipe, an error msg will be caused
sub set_value{
	my $self = $_[0];
	my $whichpart = $self->determine_whichpart($_[4]);

	donstdlib::check_string($_[2],'\|');			#check key against pipes
	donstdlib::check_string($_[3],'\|');			#check value against pipes
	
	#ignore changes of blockid
	if($_[2] ne "DKVP_BID"){
		$self->{$whichpart}->[$self->get_index($_[1], $whichpart)]->{$_[2]} = $_[3];
	};
};

########################################################################################################
#this function modifies the whole block with id $_[1]
#it writes the hash reference $_[2] in it.
#$_[3] determines $whichpart.

#a block is a reference to any hash (one-dimensional(
#to add a new block, call set_block(add_block(), $my_block);
sub set_block{
	my $self = $_[0];
	my $whichpart = $self->determine_whichpart($_[3]);
	my %newblock = %{$_[2]};
	
	#check for pipes that would destroy db
	foreach my $key (keys(%newblock)){
		donstdlib::check_string($key,'\|');			#check key against pipes
		donstdlib::check_string($newblock{$key},'\|');			#check value against pipes
	}; 
	
	#prevents writes on block id
	$newblock{"DKVP_BID"} = $self->get_value($_[1],"DKVP_BID");
	$self->{$whichpart}->[$self->get_index($_[1],$whichpart)] = \%newblock;
};
########################################################################################################
#sets filename - name must be in $_[1]
#(used for write(), for example. 
sub set_filename{
	my $self = $_[0];
	$self->{'filename'} = $_[1];
};

########################################################################################################
#writes the memory version to disk.
#classical way of modifiing: read, set_value, write

#the current code wastes memory and is a bit inefficient
sub write{
	my $self = $_[0];
	my $ftxt;
	
	#do not write if protected
	if($self->{'write_protection'} == 1){
		return;
	};
	#create header
	for(my $cnt = 0; $cnt < $self->{'info'}->{'header'}->{'nrofblocks'}; $cnt++){

		#it is necessary to set the Block IDs manually as it must be placed first in a block
		$ftxt .= "DKVP_BID|$self->{'header'}->[$cnt]->{'DKVP_BID'}|";

		foreach my $key (keys(%{$self->{'header'}->[$cnt]})){
			if($key ne "DKVP_BID"){		#block id's are set manually
				$ftxt .= "$key|$self->{'header'}->[$cnt]->{$key}|";			
			};
		}; 	
	};
	
	#chop($ftxt);			#cut away the last Pipe which is added by the stupid loop 
	$ftxt .= "\n\n";		#separate the header from the data
	
	#create the footer
	for(my $cnt = 0; $cnt < @{$self->{'data'}}; $cnt++){
		
		#it is necessary to set the Block IDs manually as it must be placed first in a block
		$ftxt .= "DKVP_BID|$self->{'data'}->[$cnt]->{'DKVP_BID'}|";
		
		foreach my $key (keys(%{$self->{'data'}->[$cnt]})){
			if($key ne "DKVP_BID"){		#block id's are set manually
				$ftxt .= "$key|$self->{'data'}->[$cnt]->{$key}|";			
			};
		}; 	
	};
	
	#chop($ftxt);			#cut away the last Pipe which is added by the stupid loop 
	
	#write file
	donstdlib::write_file($self->{'filename'},$ftxt);
	
	};
	
########################################################################################################
#this function returns the bigges block ID which is used.
#$_[1] can be "data" for data part (footer) or "header" for header. 
#When empty (or anything different from "header"), footer data will be used

sub get_biggest_BID{
	my $self = $_[0];
	my $whichpart = $self->determine_whichpart($_[1]);
	
	my $biggestBID = -1;
	
	for(my $cnt = 0; $cnt < @{$self->{$whichpart}}; $cnt++){
		foreach my $key (keys(%{$self->{$whichpart}->[$cnt]})){
			if($key eq "DKVP_BID"){		
				if($self->{$whichpart}->[$cnt]->{$key} > $biggestBID){
					$biggestBID = $self->{$whichpart}->[$cnt]->{$key};
				};
			};
		}; 	
	};
	
	return $biggestBID;
};
########################################################################################################
#this function creates a new block and returns the new block id
#$_[1] can be "data" for data part (footer) or "header" for header. 
#When empty (or anything different from "header"), footer data will be used

sub add_block{
	my $self = $_[0];

	my $whichpart = $self->determine_whichpart($_[1]);

	#calculate new Block ID
	my $BID = $self->get_biggest_BID($whichpart);		#get biggest BID in use
	$BID++;															#increment it
	
	my $arraysize = @{$self->{$whichpart}};				#get arraysize for getting new index
	
	$self->{$whichpart}->[$arraysize]->{'DKVP_BID'} = $BID;
	$self->{'info'}->{$whichpart}->{'nrofblocks'}++;
	return $BID;	
};

########################################################################################################
#deletes the block with id $_[1]
#$_[2] can be "header" or "data" or emty
sub delete_block{
	my $self = $_[0];
	my $whichpart = $self->determine_whichpart($_[2]);
	splice(@{$self->{$whichpart}},$self->get_index($_[1],$whichpart),1);											#delete it from outdated position
	$self->{'info'}->{$whichpart}->{'nrofblocks'}--;
};

########################################################################################################
#deletes the key $_[1] with its value.
#when $_[2] is set, it will delete it only from the block with the id $_[2].
#whichpart (see function) in $_[3]
sub delete_key{
	my $self = $_[0];
	my $whichpart = $self->determine_whichpart($_[3]);

	#just from one posting
	
	if($_[2] ne ""){
		delete $self->{$whichpart}->[$self->get_index($_[2],$whichpart)]->{$_[1]};
	}else{
		for(my $cnt = 0; $cnt < @{$self->{$whichpart}};$cnt++){
				delete $self->{$whichpart}->[$cnt]->{$_[1]};
		};
	};
};

########################################################################################################
#moves the block with id $_[1] to array index $_[2], $_[3] specifies (see function) $whichpart
#the the others in the array will move on
#$_[0] is the uppermost item, $_[$#{$self->{$whichpart}] the lowermost/last one
sub move_BID_to_index{
	my $self = $_[0];
	my $whichpart = determine_whichpart($_[3]);
	my $oldindex = $self->get_index($_[1],$whichpart);
	my $newindex = $_[2];
	
	#check against array exceeding
	if(($newindex > @{$self->{$whichpart}})||($newindex < 0)){
		donstdlib::error("dkvp_invalidindex",$newindex);
	};
	
	my %tmp = %{$self->{$whichpart}->[$oldindex]};										#make temporary copy as it will be deleted
	splice(@{$self->{$whichpart}},$oldindex,1);											#delete it from outdated position
	
	#now the array is shorter -> position to insert must be recalculated
	if($newpos >= $oldindex){
		$newpos--;
	};
	splice(@{$self->{$whichpart}},$_[2],0,\%tmp);										#insert at new position
}; 


true;

