<commit_before><?php

class ICalendarParser {

	private $filehandle;
	private $currentline;
	private $linebuffer = "";

	public function parse($filehandle){
		$this->filehandle = $filehandle;
		$this->next_content_line();
		$this->next_content_line();
		$cal = $this->parse_component();
		if($cal===FALSE) return FALSE;
		return $cal;
	}
	
	private function parse_component(){
		if($this->currentline===FALSE || $this->currentline["name"] != "begin"){
			return FALSE;
		}
		$name = $this->currentline["value"];
		$props = array();
		$comps = array();
		$this->next_content_line();
		while(TRUE){
			if($this->currentline === FALSE) return FALSE;
			$comp = $this->parse_component();
			if($comp!==FALSE){
				array_push($comps,$comp);
				$this->next_content_line();
			}			
			if($this->currentline["name"]=="end" && $this->currentline["value"]==$name){
				$this->next_content_line();
				break;
			}
			array_push($props,$this->currentline);
			$this->next_content_line();
		}
		return array("name"=>$name,"properties"=>$props,"components"=>$comps);
	}
	
	private function next_content_line(){
		if($this->linebuffer === FALSE){
			$this->currentline = FALSE;
			return;
		}
		while(TRUE){
			$fline = fgets($this->filehandle);
			if($fline === FALSE){
				if(strlen($this->linebuffer)>0){
					$this->currentline = $this->parse_content_line($this->linebuffer);
				}
				$this->linebuffer = FALSE;
				break;
			}elseif(substr($fline,0,1)==" "){
				$this->linebuffer .= substr($fline,1,-2);
			}else{
				if(strlen($this->linebuffer)>0){
					$this->currentline = $this->parse_content_line($this->linebuffer);
				}
				$this->linebuffer = substr($fline,0,-2);
				break;
			}
		}
	}
	
	// TODO: content value should be an array of values, where non-escaped comma is separator
	private function parse_content_line($line){
		$pos = 0;
		$name = $this->parse_name($line,$pos);
		if($name===FALSE) {
			//echo "no name";
			return FALSE;
		}
		$params = array();
		while(TRUE){
			$param = $this->parse_param($line,$pos);
			if($param===FALSE) break;
			array_push($params,$param);
		}
		$c = $this->expect($line,$pos,":",NULL);
		if($c===FALSE){
			//echo "no colon";
			return FALSE;
		}
		$value = $this->parse_value($line,$pos);
		if($value===FALSE){
			//echo "no value";
			return FALSE;
		}
		return array("name"=>$name, "params"=>$params, "value"=>$value);
	}
	
	private function parse_param($line,&$pos){
		//echo "parsing param at $pos";
		$c = $this->expect($line,$pos,";",NULL);
		if($c===FALSE) return FALSE;
		$name = $this->parse_name($line,$pos);
		if($name===FALSE) return FALSE;
		$c = $this->expect($line,$pos,"=",NULL);
		if($c===FALSE) return FALSE;
		$value = $this->parse_param_value($line,$pos);
		if($value===FALSE) return FALSE;
		return array("name"=>$name,"value"=>$value);
	}
	
	private function parse_param_value($line,&$pos){
		//echo "parsing param value at $pos";
		$disallowed = "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x0a\x0b\x0c\x0d\x0e\x0f"
			."\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x7f\"";
		$buffer = "";
		if($this->current_char($line,$pos)=='"'){
			# quoted string
			$c = $this->expect($line,$pos,'"',NULL);
			if($c===FALSE) return FALSE;
			while(TRUE){
				$c = $this->expect($line,$pos,NULL,$disallowed);
				if($c===FALSE) break;
				$buffer .= $c;
			}
			$c = $this->expect($line,$pos,'"',NULL);
			if($c===FALSE) return FALSE;
		}else{
			# unquoted content
			while(TRUE){
				$c = $this->expect($line,$pos,NULL,$disallowed.';:,');
				if($c===FALSE) break;
				$buffer .= $c;
			}
		}
		return $buffer;
	}
	
	private function parse_value($line,&$pos){
		$buffer = "";
		while(TRUE){
			$c = $this->expect($line,$pos,NULL,NULL);
			if($c===FALSE) break;
			$buffer .= $c;
		}
		return $buffer;
	}
	
	private function parse_name($line,&$pos){
		//echo "parsing name at $pos";
		$allowed = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-";
		$buffer = "";
		$c = $this->expect($line,$pos,$allowed,NULL);
		if($c===FALSE) return FALSE;
		$buffer .= $c;
		while(TRUE){
			$c = $this->expect($line,$pos,$allowed,NULL);
			if($c===FALSE) break;
			$buffer .= $c;
		}
		return strtolower($buffer);		
	}
	
	private function expect($line,&$pos,$allowed,$disallowed){
		$curr = $this->current_char($line,$pos);
		//echo "expecting $allowed and not $disallowed, got $curr";
		if($curr === FALSE) return FALSE;
		if($disallowed!==NULL && strpos($disallowed,$curr)!==FALSE){
			//echo "$curr was disallowed";
			return FALSE;
		}
		if($allowed!==NULL && strpos($allowed,$curr)===FALSE){
			//echo "$curr was not allowed";
			return FALSE;
		}
		$this->next_char($line,$pos);
		return $curr;
	}
	
	private function current_char($line,$pos){
		return $pos<strlen($line) ? $line[$pos] : FALSE;
	}
	
	private function next_char($line,&$pos){
		if($pos<strlen($line)){
			$pos += 1;
		}
	}
	
}

$handle = @fopen("example.ics","r");
$parser = new ICalendarParser();
$cal = $parser->parse($handle);
echo serialize($cal);
fclose($handle);<commit_msg>wip ical tree to event data structure<commit_after><?php

class ICalendarParser {

	private $filehandle;
	private $currentline;
	private $linebuffer = "";

	public function parse($filehandle){
		$this->filehandle = $filehandle;
		$this->next_content_line();
		$this->next_content_line();
		$cal = $this->parse_component();
		if($cal===FALSE) return FALSE;
		return $cal;
	}
	
	private function parse_component(){
		if($this->currentline===FALSE || $this->currentline["name"] != "begin"){
			return FALSE;
		}
		$name = $this->currentline["values"][0];
		$props = array();
		$comps = array();
		$this->next_content_line();
		while(TRUE){
			if($this->currentline === FALSE) return FALSE;
			$comp = $this->parse_component();
			if($comp!==FALSE){
				if(!isset($comps[$comp["name"]])) $comps[$comp["name"]] = array();
				array_push($comps[$comp["name"]],$comp);
			}			
			if($this->currentline["name"]=="end" && $this->currentline["values"][0]==$name){
				$this->next_content_line();
				break;
			}
			if(!isset($props[$this->currentline["name"]])) $props[$this->currentline["name"]] = array();
			array_push($props[$this->currentline["name"]],$this->currentline);
			$this->next_content_line();
		}
		return array("name"=>$name,"properties"=>$props,"components"=>$comps);
	}
	
	private function next_content_line(){
		if($this->linebuffer === FALSE){
			$this->currentline = FALSE;
			return;
		}
		while(TRUE){
			$fline = fgets($this->filehandle);
			if($fline === FALSE){
				if(strlen($this->linebuffer)>0){
					$this->currentline = $this->parse_content_line($this->linebuffer);
				}
				$this->linebuffer = FALSE;
				break;
			}elseif(substr($fline,0,1)==" "){
				$this->linebuffer .= substr($fline,1,-2);
			}else{
				if(strlen($this->linebuffer)>0){
					$this->currentline = $this->parse_content_line($this->linebuffer);
				}
				$this->linebuffer = substr($fline,0,-2);
				break;
			}
		}
	}
	
	private function parse_content_line($line){
		$pos = 0;
		$name = $this->parse_name($line,$pos);
		if($name===FALSE) {
			//echo "no name";
			return FALSE;
		}
		$params = array();
		while(TRUE){
			$param = $this->parse_param($line,$pos);
			if($param===FALSE) break;
			if(!isset($params[$param["name"]])) $params[$param["name"]] = array();
			array_push($params[$param["name"]],$param);
		}
		$c = $this->expect($line,$pos,":",NULL);
		if($c===FALSE){
			//echo "no colon";
			return FALSE;
		}
		$values = $this->parse_values($line,$pos);
		if($values===FALSE){
			//echo "no values";
			return FALSE;
		}
		return array("name"=>$name, "params"=>$params, "values"=>$values);
	}
	
	private function parse_param($line,&$pos){
		//echo "parsing param at $pos";
		$c = $this->expect($line,$pos,";",NULL);
		if($c===FALSE) return FALSE;
		$name = $this->parse_name($line,$pos);
		if($name===FALSE) return FALSE;
		$c = $this->expect($line,$pos,"=",NULL);
		if($c===FALSE) return FALSE;
		$value = $this->parse_param_value($line,$pos);
		if($value===FALSE) return FALSE;
		return array("name"=>$name,"value"=>$value);
	}
	
	private function parse_param_value($line,&$pos){
		//echo "parsing param value at $pos";
		$disallowed = "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x0a\x0b\x0c\x0d\x0e\x0f"
			."\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x7f\"";
		$buffer = "";
		if($this->current_char($line,$pos)=='"'){
			# quoted string
			$c = $this->expect($line,$pos,'"',NULL);
			if($c===FALSE) return FALSE;
			while(TRUE){
				$c = $this->expect($line,$pos,NULL,$disallowed);
				if($c===FALSE) break;
				$buffer .= $c;
			}
			$c = $this->expect($line,$pos,'"',NULL);
			if($c===FALSE) return FALSE;
		}else{
			# unquoted content
			while(TRUE){
				$c = $this->expect($line,$pos,NULL,$disallowed.';:,');
				if($c===FALSE) break;
				$buffer .= $c;
			}
		}
		return $buffer;
	}
	
	private function parse_values($line,&$pos){
		$vals = array();
		$buffer = "";
		$escape = FALSE;
		while(TRUE){
			$curr = $this->current_char($line,$pos);
			if($curr===FALSE) break;
			if($escape){
				switch($curr){
					case ";": case ",": case "\\": $buffer .= $curr; break;
					case "n": case "N": $buffer .= "\n"; break;
					default: $buffer .= "\\".$curr; break;
				}
				$escape = FALSE;
			}else{
				if($curr=="\\"){
					$escape = TRUE;
				}elseif($curr==","){
					array_push($vals,$buffer);
					$buffer = "";
				}else{
					$buffer .= $curr;
				}
			}	
			$this->next_char($line,$pos);		
		}
		array_push($vals,$buffer);
		return $vals;
	}
	
	private function parse_name($line,&$pos){
		//echo "parsing name at $pos";
		$allowed = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-";
		$buffer = "";
		$c = $this->expect($line,$pos,$allowed,NULL);
		if($c===FALSE) return FALSE;
		$buffer .= $c;
		while(TRUE){
			$c = $this->expect($line,$pos,$allowed,NULL);
			if($c===FALSE) break;
			$buffer .= $c;
		}
		return strtolower($buffer);		
	}
	
	private function expect($line,&$pos,$allowed,$disallowed){
		$curr = $this->current_char($line,$pos);
		//echo "expecting $allowed and not $disallowed, got $curr";
		if($curr === FALSE) return FALSE;
		if($disallowed!==NULL && strpos($disallowed,$curr)!==FALSE){
			//echo "$curr was disallowed";
			return FALSE;
		}
		if($allowed!==NULL && strpos($allowed,$curr)===FALSE){
			//echo "$curr was not allowed";
			return FALSE;
		}
		$this->next_char($line,$pos);
		return $curr;
	}
	
	private function current_char($line,$pos){
		return $pos<strlen($line) ? $line[$pos] : FALSE;
	}
	
	private function next_char($line,&$pos){
		if($pos<strlen($line)){
			$pos += 1;
		}
	}
	
}

function echo_component($comp,$indent){
	for($i=0;$i<$indent;$i++) echo "\t";
	echo $comp["name"]."\n";
	foreach($comp["properties"] as $propname=>$props){
		for($i=0;$i<$indent+1;$i++) echo "\t";
		echo $propname."[";
		foreach($props as $prop){
			echo "(";
			foreach($prop["params"] as $paramname=>$params){
				echo $paramname."=[";
				foreach($params as $param){
					echo $param["value"]."|";
				}
			}
			echo ") : ";
			foreach($prop["values"] as $value){
				echo $value."|";
			}
		}
		echo "]\n";
	}
	foreach($comp["components"] as $compname=>$comps){
		for($i=0;$i<$indent+1;$i++) echo "\t";
		echo $compname."\n";
		foreach($comps as $inner){
			echo_component($inner,$indent+2);
		}
	}
}

function cal_to_event_data($cal){
	$calobj = new stdClass();
	$calobj->events = array();
	$calobj->{"recurring-events"} = array();
	if($cal["name"]!="VCALENDAR"){
		return "Expected VCALENDAR component but found".$cal["name"];
	}
	if(isset($cal["properties"]["calscale"]) 
			&& strtolower($cal["properties"]["calscale"][0]["values"][0]) != "gregorian"){
		return "Non-gregorian calendar not supported";
	}
	if(isset($cal["components"]["VEVENT"])){
		foreach($cal["components"]["VEVENT"] as $vevent){
			$eventobj = new stdClass();
			if(isset($vevent["properties"]["summary"])){
				$eventobj->name = $vevent["properties"]["summary"][0]["values"][0];
			}else{
				$eventobj->name = "Unnamed event";
			}
			if(isset($vevent["properties"]["description"]) 
					&& strlen($vevent["properties"]["description"][0]["values"][0])>0){
				$eventobj->description = $vevent["properties"]["description"][0]["values"][0];
			}
			// TODO: URL?
			if(isset($vevent["properties"]["rrule"])){
				// TODO: recurrence
			}else{
				// TODO: date
			}
			// TODO: time
			// TODO: duration
			
			if(isset($eventobj->recurrence)){
				array_push($calobj->{"recurring-events"},$eventobj);
			}else{
				array_push($calobj->events,$eventobj);
			}
		}
	}
	return $calobj;
}

$handle = @fopen("example.ics","r");
$parser = new ICalendarParser();
$cal = $parser->parse($handle);
if($cal===FALSE){
	echo "Failed to parse";
}else{
	//echo_component($cal,0);
	$caldata = cal_to_event_data($cal);
	if(is_string($caldata)){
		echo "ERROR: $caldata\n";
	}else{
		foreach($caldata->events as $event){
			echo $event->name."\n";
		}
	}
}
fclose($handle);
{'fraction_non_alphanumeric': 0.22932447055186272, 'fraction_numerical': 0.012366671819446591, 'mean_word_length': 4.236341562120599, 'pattern_counts': {'":': 7, '<': 12, '<?xml version=': 0, '>': 138, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7597668', 'n_tokens_mistral': 5552, 'n_tokens_neox': 4813, 'n_words': 790}