Your IP : 216.73.217.53


Current Path : /home/bonneesp/www/plugins/editors-xtd/tabulizer/tabulizer/assets/
Upload File :
Current File : /home/bonneesp/www/plugins/editors-xtd/tabulizer/tabulizer/assets/class.php

<?php
/**
 * @version		6.5.0 tabulizer $
 * @package		tabulizer
 * @copyright	Copyright © 2011 - All rights reserved.
 * @license		GNU/GPL
 * @author		Dimitrios Mourloukos
 * @author mail	info@alterora.gr
 * @website		www.tabulizer.com
 * 
 */


// no direct access
defined('_JEXEC') or die('Restricted access');

require_once(TABULIZER_INCLUDE_PATH . 'assets' .DIRECTORY_SEPARATOR. 'helper.php');

class Tabulizer {

	var $rules_dir = '.';
	var $use_comments = false;
	var $strict_column_consistency = true;
	
	function setDir($rules_dir) {
		$this->rules_dir = $rules_dir;
	}
	
	function setComments($value) {
		$this->use_comments = $value;
	}
	
	function setColumnConsistency($value) {
		$this->strict_column_consistency = $value;
	}

	function parseInput($text, $separator, $enclosure, &$rows) {
		if (empty($text) || empty($separator)) return;
		$escape = '"';
		
		$row_id = 1;
		$lines = explode("\n", $text);
		foreach ($lines as $line) {
			$line = trim($line); if (empty($line)) continue;						
			$columns = TabulizerString::my_str_getcsv ( $line, $separator, $enclosure, $escape);
			$column_id = 1;
			foreach ($columns as $cell) {
				$rows[$row_id][$column_id] = $cell;
				$column_id++;
			}	
			$row_id++;			
		}
	}
	
	function validateTableRows(&$rows, &$errors) {
		if (empty($rows)) {
			$errors[] = JText::_('COM_TABULIZER_NO_INPUT_TEXT');
			return false;
		}	
		
		$column_count = 0;
		$column_count_row = 0;
		$column_count_max = 0;
		$fix_column_consistency = false;
		
		foreach ($rows as $row_id => $row) {
			$count = count($row);
			if ($column_count == 0) {
				$column_count = $count;
				$column_count_row = $row_id;
			} else if ($count != $column_count) {
				if ($this->strict_column_consistency) {
					$errors[] = sprintf(JText::_('COM_TABULIZER_COLUMNS_COUNT_DISCREPANCY'), $column_count_row, $column_count, $row_id, $count); // ERROR_CODE_NUM_OF_COLUMN_DISCREPANCY;								
					return false;
				} else {
					$fix_column_consistency = true;
				}
			}
			if ($count > $column_count_max) $column_count_max = $count;
		}
		
		if ($fix_column_consistency) {
			foreach ($rows as $row_id => &$row) {
				$count = count($row);
				while ($count < $column_count_max) {
					$row[] = '';
					$count++;
				}
			}
		}
		
		return true;
	}
		
	function parseRowColumnRange($value, $row_id=null, $column_id=null) {				
		$value = str_replace(' ','',$value);
		if (empty($value)) return null;		
		
		$range = null;
		$row_from = $row_to = $column_from = $column_to = 0;
		
		list($row_range, $column_range) = explode(',', $range);		
		if (!empty($row_range)) {
			list($row_from,$row_to) = explode(':',$row_range);		
			if (!empty($row_from)) {
				if ($row_from = 'SAME') $row_from = $row_id;
				else if ($row_from = 'ALL') $row_from = 0;
				else $row_from = intval($row_from); 
			}	
			if (!empty($row_from)) $range['row_from'] = $row_from; 
			
			if (!empty($row_to)) {
				if ($row_to = 'SAME') $row_to = $row_id;
				else if ($row_to = 'ALL') $row_to = 0;
				else $row_to = intval($row_to); 
			}	
			if (!empty($row_to)) $range['row_to'] = $row_to; 
		}	
		
		if (!empty($column_range)) {
			list($column_from,$column_to) = explode(':',$column_range);		
			if (!empty($column_from)) {
				if ($column_from = 'SAME') $column_from = $column_id;
				else if ($column_from = 'ALL') $column_from = 0;
				else $column_from = intval($column_from); 
			}	
			if (!empty($column_from)) $range['column_from'] = $column_from; 
			
			if (!empty($column_to)) {
				if ($column_to = 'SAME') $column_to = $column_id;
				else if ($column_to = 'ALL') $column_to = 0;
				else $column_to = intval($column_to); 
			}	
			if (!empty($column_to)) $range['column_to'] = $column_to; 
		}
		
		// adjust range limits
		if (empty($row_from)) {
			if (!empty($row_to)) $row_from = 1;
		}
		if (empty($row_to))	{
			if (!empty($row_from)) $row_to = $row_from;
		}			
		if (empty($column_from)) {
			if (!empty($column_to)) $column_from = 1;
		}
		if (empty($column_to))	{
			if (!empty($column_from)) $column_to = $column_from;
		}			
			
		$range = array('str'=> $value, 'row_from'=>$row_from, 'row_to'=>$row_to, 'column_from'=>$column_from, 'column_to'=>$column_to);
		
		return $range; 						
	}	
	
	function is_my_numeric($str) {
		return TabulizerMath::getNumericValue($str);										
	}
	
	function getValuesCalculation($values, $op) {
		$value = '';
		if (empty($values)) return $value;
		
		switch ($op) {
			case 'add': 
				$sum = 0;
				foreach ($values as $value) {					
					$sum += $value;
				}
				$value = $sum;
				break; 
			case 'mul':
				$sum = 1;
				foreach ($values as $value) {					
					$sum *= $value;
				}
				$value = $sum;
				break;				
			case 'avg':
				$sum = 0;
				foreach ($values as $value) {					
					$sum += $value;
				}
				$value = ($sum / count($values));				
				break;
			case 'med':
				sort($values, SORT_NUMERIC);
				$median_index = round(count($values) /2, 0, PHP_ROUND_HALF_DOWN);	
				$value = $values[$median_index];
				break;
			case 'min': 
				$value = min($values); 
				break;
			case 'max': 
				$value = max($values); 
				break;
		}
		
		return $value;
	}
	
	function getCellCalculation($calculation, $row_id, $column_id, &$rows) {	
		// sanity check
		if (empty($rows)) return null;		
			
		// backward compatibility
		if (is_numeric($calculation)) {	
			switch ($calculation) {
				case CALCULATION_ADD_ABOVE: $i_from = 1; $i_to = $row_id-1; $j_from = $column_id; $j_to = $column_id; $op = 'add'; break;
				case CALCULATION_ADD_BELOW: $i_from = $row_id+1;  $i_to = count($rows); $j_from = $column_id; $j_to = $column_id; $op = 'add'; break;
				case CALCULATION_ADD_LEFT: $i_from = $row_id; $i_to = $row_id; $j_from = 1; $j_to = $column_id-1; $op = 'add'; break;
				case CALCULATION_ADD_RIGHT: $i_from = $row_id; $i_to = $row_id; $j_from = $column_id+1; $j_to = count($rows[0]); $op = 'add'; break;
				case CALCULATION_MUL_ABOVE: $i_from = 1; $i_to = $row_id-1; $j_from = $column_id; $j_to = $column_id; $op = 'mul'; break;
				case CALCULATION_MUL_BELOW: $i_from = $row_id+1;  $i_to = count($rows); $j_from = $column_id; $j_to = $column_id; $op = 'mul'; break;
				case CALCULATION_MUL_LEFT: $i_from = $row_id; $i_to = $row_id; $j_from = 1; $j_to = $column_id-1; $op = 'mul'; break;
				case CALCULATION_MUL_RIGHT: $i_from = $row_id; $i_to = $row_id; $j_from = $column_id+1; $j_to = count($rows[0]); $op = 'mul'; break;
				case CALCULATION_AVG_ABOVE: $i_from = 1; $i_to = $row_id-1; $j_from = $column_id; $j_to = $column_id; $op = 'avg'; break;
				case CALCULATION_AVG_BELOW: $i_from = $row_id+1;  $i_to = count($rows); $j_from = $column_id; $j_to = $column_id; $op = 'avg'; break;
				case CALCULATION_AVG_LEFT: $i_from = $row_id; $i_to = $row_id; $j_from = 1; $j_to = $column_id-1; $op = 'avg'; break;
				case CALCULATION_AVG_RIGHT: $i_from = $row_id; $i_to = $row_id; $j_from = $column_id+1; $j_to = count($rows[0]); $op = 'avg'; break;
				case CALCULATION_MED_ABOVE: $i_from = 1; $i_to = $row_id-1; $j_from = $column_id; $j_to = $column_id; $op = 'med'; break;
				case CALCULATION_MED_BELOW: $i_from = $row_id+1;  $i_to = count($rows); $j_from = $column_id; $j_to = $column_id; $op = 'med'; break;
				case CALCULATION_MED_LEFT: $i_from = $row_id; $i_to = $row_id; $j_from = 1; $j_to = $column_id-1; $op = 'med'; break;
				case CALCULATION_MED_RIGHT: $i_from = $row_id; $i_to = $row_id; $j_from = $column_id+1; $j_to = count($rows[0]); $op = 'med'; break;
				case CALCULATION_MIN_ABOVE: $i_from = 1; $i_to = $row_id-1; $j_from = $column_id; $j_to = $column_id; $op = 'min'; break;
				case CALCULATION_MIN_BELOW: $i_from = $row_id+1;  $i_to = count($rows); $j_from = $column_id; $j_to = $column_id; $op = 'min'; break;
				case CALCULATION_MIN_LEFT: $i_from = $row_id; $i_to = $row_id; $j_from = 1; $j_to = $column_id-1; $op = 'min'; break;
				case CALCULATION_MIN_RIGHT: $i_from = $row_id; $i_to = $row_id; $j_from = $column_id+1; $j_to = count($rows[0]); $op = 'min'; break;
				case CALCULATION_MAX_ABOVE: $i_from = 1; $i_to = $row_id-1; $j_from = $column_id; $j_to = $column_id; $op = 'max'; break;
				case CALCULATION_MAX_BELOW: $i_from = $row_id+1;  $i_to = count($rows); $j_from = $column_id; $j_to = $column_id; $op = 'max'; break;
				case CALCULATION_MAX_LEFT: $i_from = $row_id; $i_to = $row_id; $j_from = 1; $j_to = $column_id-1; $op = 'max'; break;
				case CALCULATION_MAX_RIGHT: $i_from = $row_id; $i_to = $row_id; $j_from = $column_id+1; $j_to = count($rows[0]); $op = 'max'; break;			
				default: return null;
			}
			
			$values = array();
			for ($i=$i_from;$i<=$i_to;$i++) {
				for ($j=$j_from;$j<=$j_to;$j++) {
					$value = $rows[$i][$j];
					$numeric_value = $this->is_my_numeric(trim($value));
					if (is_numeric($numeric_value)) {
						$values[] = $numeric_value;
					}
				}
			}
			
			if (empty($values)) return null;		
			else return $this->getValuesCalculation($values, $op);
			
		} else {			
			$calculation_value = null;
			
			$function_name = null;
			$args_element = null;
			$args_range = null;
			$custom_arg = null;
				
			$parts = explode(CALCULATION_SEP,$calculation);
			if (count($parts)==4) {
				$function_name = $parts[0];
				$element = $parts[1];
				$element_range = $parts[2];								
				$custom_arg = $parts[3];
				
				$element_range_ids = null;
				$values = array();
				
				switch ($element) {
					case 'row':
						$last = count($rows[1]);
						$element_range_ids = $this->getRangeIds($element_range, 0, $last, null, null);
						if ($element_range_ids) {
							foreach ($element_range_ids as $id) {
								$value = $rows[$row_id][$id];
								$values["{$row_id},{$id}"] = $value;
							}
						}
						break;					
					case 'column':
						$last = count($rows);
						$element_range_ids = $this->getRangeIds($element_range, 0, $last, null, null);
						if ($element_range_ids) {
							foreach ($element_range_ids as $id) {
								$value = $rows[$id][$column_id];																
								$values["{$id},{$column_id}"] = $value;
							}
						} 
						break;						
					case 'cell':
						$element_range_ids = $this->getCellRangeIds($element_range, $rows);
						if ($element_range_ids) {
							foreach ($element_range_ids as $id) {
								list($rid, $cid) = explode(',',$id);
								$value = $rows[$rid][$cid];
								$values["{$rid},{$cid}"] = $value;								
							}
						}							
						break;							
				}									
				
				if (!empty($function_name) && !empty($values)) {
					$function_name = strtolower($function_name);
					// sanity check															
					if (preg_match('/^([a-z\d_]+)$/',$function_name,$matches)) {
						$function_file = JPATH_SITE .DIRECTORY_SEPARATOR. RULES_CALC_DIR . $function_name . '.php';
						if (file_exists($function_file)) {
							require_once($function_file);
							$class_name = 'TabulizerCalc'.ucfirst($function_name);																					
							if (class_exists($class_name)) {								
								$class = new $class_name();
								if (method_exists($class, 'calc')) {									
									$calculation_value = $class->calc($row_id, $column_id, $values, $custom_arg);
								}
							}
						}
					}
					
				}
			}
			
			return $calculation_value;
			
		}								
						
	}
	
	function my_ucfirst($string) {	
        $string = mb_strtoupper(mb_substr($string, 0, 1)) . mb_substr($string, 1);
        return $string;
    }
	
	function my_ucwords($string) {	
		$words = explode(' ',$string);
		foreach ($words as &$word) {
			$word = my_ucfirst($word);
		}		
        $string = implode(' ',$words);
        return $string;
    }
	
	function createAutospanTable($rows, &$autospan_table) {
		if (empty($rows)) return;
		
		$num_of_rows = count($rows);
		$num_of_columns = count($rows[1]);
		
		$area = 'columns';
		for ($row_id = 1; $row_id <= $num_of_rows; $row_id++) {								
			$counter = 1;			
			for ($column_id = 1; $column_id <= $num_of_columns; $column_id++) {							
				$cell = trim($rows[$row_id][$column_id]);																							
				if (!empty($cell)) {						
					$counter++;
				} else if (!$counter) {
					$counter = 1;
				}
				$autospan_table[$area][$row_id][$column_id] = $counter;
			}	
		}	
		
		$area = 'rows';
		for ($column_id = 1; $column_id <= $num_of_columns; $column_id++) {								
			$counter = 0;			
			for ($row_id = 1; $row_id <= $num_of_rows; $row_id++) {			
				$cell = trim($rows[$row_id][$column_id]);																							
				if (!empty($cell)) {						
					$counter++;
				} else if (!$counter) {
					$counter = 1;
				}
				$autospan_table[$area][$row_id][$column_id] = $counter;				
			}	
		}											
			
	}
	
	function parseRangeStr($range_str, $range_key, $last, $area, $autospan_table) {
		$ids = array();
		
		$parts = explode(',',$range_str);
		foreach ($parts as $range) {					
		
			// replace L\d with actual number
			while (preg_match( '/(\W|^)L([\d]+)(\W|$)/', $range, $matches)) {	
				$l_id = $last - intval($matches[2]) + 1;
				$replace_from = $matches[0];	
				$replace_to = $matches[1] . $l_id . $matches[3];
				$range = str_replace($replace_from,$replace_to,$range);
			}
			
			if (is_numeric($range)) {
				if ($range_key) {
					for ($id=1;$id<=$last;$id++) {
						$range_id = $this->getRangeKeyId($id, $range_key, $area, $autospan_table);
						if ($range_id == $range) $ids[] = $id;
					}
				} else {
					$range = intval($range);
					if ($range <= $last) {
						$ids[] = $range;
					}
				}					
			} else if (preg_match( '/^([\d]+)-([\d]+)$/', $range, $matches)) {
				$from = intval($matches[1]);
				$to = intval($matches[2]);				
				if ($range_key) {
					for ($id=1;$id<=$last;$id++) {
						$range_id = $this->getRangeKeyId($id, $range_key, $area, $autospan_table);
						if (($range_id >= $from)&&($range_id<=$to)) $ids[] = $id;
					}
				} else {
					$to = ($last<$to)?$last:$to;
					for ($id=$from; $id<=$to;$id++) {
						$ids[] = $id;
					}
				}
			} else if (preg_match( '/^>([\d]+)$/', $range, $matches)) {
				$from = intval($matches[1])+1;					
				$to = $last;
				if ($range_key) {
					for ($id=1;$id<=$last;$id++) {
						$range_id = $this->getRangeKeyId($id, $range_key, $area, $autospan_table);
						if (($range_id >= $from)&&($range_id<=$to)) $ids[] = $id;
					}
				} else {
					for ($id=$from; $id<=$to;$id++) {
						$ids[] = $id;
					}
				}		
			} else if (preg_match( '/^<([\d]+)$/', $range, $matches)) {
				$from = 1;
				$to = intval($matches[1])-1;				
				if ($range_key) {
					for ($id=1;$id<=$last;$id++) {
						$range_id = $this->getRangeKeyId($id, $range_key, $area, $autospan_table);
						if (($range_id >= $from)&&($range_id<=$to)) $ids[] = $id;
					}
				} else {
					$to = ($last<$to)?$last:$to;				
					for ($id=$from; $id<=$to;$id++) {
						$ids[] = $id;
					}
				}										
			} else if (preg_match( '/^>=([\d]+)$/', $range, $matches)) {
				$from = intval($matches[1]);					
				$to = $last;
				if ($range_key) {
					for ($id=1;$id<=$last;$id++) {
						$range_id = $this->getRangeKeyId($id, $range_key, $area, $autospan_table);
						if (($range_id >= $from)&&($range_id<=$to)) $ids[] = $id;
					}
				} else {
					for ($id=$from; $id<=$to;$id++) {
						$ids[] = $id;
					}
				}		
			} else if (preg_match( '/^<=([\d]+)$/', $range, $matches)) {
				$from = 1;
				$to = intval($matches[1]);				
				if ($range_key) {
					for ($id=1;$id<=$last;$id++) {
						$range_id = $this->getRangeKeyId($id, $range_key, $area, $autospan_table);
						if (($range_id >= $from)&&($range_id<=$to)) $ids[] = $id;
					}
				} else {
					$to = ($last<$to)?$last:$to;
					for ($id=$from; $id<=$to;$id++) {
						$ids[] = $id;
					}
				}										
			}  else if (preg_match( '/^FIRST([\d]+)$/', $range, $matches)) {
				$from = 1;
				$to = intval($matches[1]);
				if ($range_key) {
					for ($id=1;$id<=$last;$id++) {
						$range_id = $this->getRangeKeyId($id, $range_key, $area, $autospan_table);
						if (($range_id >= $from)&&($range_id<=$to)) $ids[] = $id;
					}
				} else {
					$to = ($last<$to)?$last:$to;
					for ($id=$from; $id<=$to;$id++) {
						$ids[] = $id;
					}
				}					
			} else if (preg_match( '/^LAST([\d]+)$/', $range, $matches)) {				
				$from = $last - intval($matches[1])+1;
				$to = $last;
				if ($range_key) {
					for ($id=1;$id<=$last;$id++) {
						$range_id = $this->getRangeKeyId($id, $range_key, $area, $autospan_table);
						if (($range_id >= $from)&&($range_id<=$to)) $ids[] = $id;
					}
				} else {					
					for ($id=$from; $id<=$to;$id++) {
						$ids[] = $id;
					}
				}					
			} else if (preg_match( '/^MOD([\d]+)$/', $range, $matches)) {				
				$from = 1;
				$to = $last;
				$mod = intval($matches[1]);
				if ($mod) {
					if ($range_key) {
						for ($id=1;$id<=$last;$id++) {
							$range_id = $this->getRangeKeyId($id, $range_key, $area, $autospan_table);
							if ($range_id % $mod == 0) $ids[] = $id;
						}
					} else {
						for ($id=$from; $id<=$to;$id++) {
							if ($id % $mod == 0) $ids[] = $id;
						}
					}
				}					
			} else if (preg_match( '/^MOD([\d]+)\[(.+)\]$/', $range, $matches)) { // backward compatibility
				$from = 1;
				$to = $last;
				$mod = intval($matches[1]);
				$subrange = $matches[2];															
				if (preg_match( '/^([\d]+)-([\d]+)$/', $subrange, $submatches)) {
					$from = intval($submatches[1]);
					$to = intval($submatches[2]);						
				} else if (preg_match( '/^>([\d]+)$/', $subrange, $submatches)) {
					$from = intval($submatches[1])+1;											
				} else if (preg_match( '/^<([\d]+)$/', $subrange, $submatches)) {
					$to = intval($submatches[1])-1;
				} else if (preg_match( '/^>=([\d]+)$/', $subrange, $submatches)) {
					$from = intval($submatches[1]);					
				} else if (preg_match( '/^<=([\d]+)$/', $subrange, $submatches)) {
					$to = intval($submatches[1]);
				}  else if (preg_match( '/^FIRST([\d]+)$/', $subrange, $submatches)) {
					$to = intval($submatches[1]);
				} else if (preg_match( '/^LAST([\d]+)$/', $subrange, $submatches)) {				
					$from = $last - intval($submatches[1])+1;
				}											
				if ($mod) {
					if ($range_key) {
						for ($id=1;$id<=$last;$id++) {
							$range_id = $this->getRangeKeyId($id, $range_key, $area, $autospan_table);
							if (($range_id - $from) % $mod == 0) $ids[] = $id;
						}
					} else {						
						for ($id=$from; $id<=$to;$id++) {
							if (($id - $from) % $mod == 0) $ids[] = $id;
						}
					}						
				}					
			} else if ($range == 'ODD') {
				$from = 1;
				$to = $last;
				if ($range_key) {
					for ($id=1;$id<=$last;$id++) {
						$range_id = $this->getRangeKeyId($id, $range_key, $area, $autospan_table);
						if ($range_id % 2) $ids[] = $id;
					}
				} else {
					for ($id=$from; $id<=$to;$id++) {
						if ($id % 2 ) $ids[] = $id;
					}
				}
			}  else if ($range == 'EVEN') {
				$from = 1;
				$to = $last;
				if ($range_key) {
					for ($id=1;$id<=$last;$id++) {
						$range_id = $this->getRangeKeyId($id, $range_key, $area, $autospan_table);
						if (!($range_id % 2)) $ids[] = $id;
					}
				} else {
					for ($id=$from; $id<=$to;$id++) {
						if (!($id % 2)) $ids[] = $id;
					}
				}
			}						
			
		}
		
		return $ids;
	}
	
	function getRangeIds($range, $range_key, $last, $area, $autospan_table) {
		$range_ids = array();
	
		$range = strtoupper(str_replace(' ','', trim($range)));						
		# <> are not allowed in the XML files, so replace them with LT and GT
		$gl_values = array('LTE','LT','GTE','GT');
		$sy_values = array('<=','<','>=','>');			
		$range = str_replace($gl_values, $sy_values, $range);		
		
		if (!empty($range)) {
			$subranges = explode(';',$range);
			foreach ($subranges as $subrange) {
				$subrange_info = array('include'=>array(), 'exclude'=>array(), 'filter'=>array());
				$subrange_str = '';
				$subrange_flag = 'include';		
				if (empty($subrange)) continue;
				$sbn = strlen($subrange);
				for ($sbi = 0; $sbi < $sbn; $sbi++) {
					$sbc = $subrange{$sbi};
					if ($sbc==RANGE_FILTER_SEP) {
						if (!empty($subrange_str)) $subrange_info[$subrange_flag][] = $this->parseRangeStr($subrange_str, $range_key, $last, $area, $autospan_table);				
						$subrange_str = '';
						$subrange_flag = 'filter';				
					} else if ($sbc==RANGE_EXCLUDE_SEP) {
						if (!empty($subrange_str)) $subrange_info[$subrange_flag][] = $this->parseRangeStr($subrange_str, $range_key, $last, $area, $autospan_table);				
						$subrange_str = '';				
						$subrange_flag = 'exclude';				
					} else {
						$subrange_str .= $sbc;
					}
				}
				$subrange_info[$subrange_flag][] = $this->parseRangeStr($subrange_str, $range_key, $last, $area, $autospan_table);
				$subrange_str = '';	
				// combine all ids together, for the current subrange
				$ids = null;
				if (!empty($subrange_info['include'])) {
					foreach ($subrange_info['include'] as $include_ids) {
						if (empty($ids)) $ids = $include_ids; else $ids = array_unique(array_merge($ids, $include_ids));
					}
				}
				if (!empty($ids)) {						
					if (!empty($subrange_info['filter'])) {
						foreach ($subrange_info['filter'] as $filter_ids) {
							$ids = array_intersect ($ids, $filter_ids); 
						}
					}
				}	
				if (!empty($ids)) {						
					if (!empty($subrange_info['exclude'])) {
						foreach ($subrange_info['exclude'] as $exclude_ids) {
							$ids = array_diff ($ids, $exclude_ids); 
						}	
					}
				}											
				if (!empty($ids)) {
					if (empty($range_ids)) $range_ids = array_unique($ids);
					else $range_ids = array_unique(array_merge($range_ids, $ids));
				}	
																												
			}
		}
		
		return $range_ids;	
	}	
	
	function getRangeKeyId($id, $range_key, $area, &$autospan_table) {				
		if ($area == 'rows') {			
			return $autospan_table['rows'][$id][$range_key];
		} else if ($area == 'columns') {
			return $autospan_table['columns'][$range_key][$id];
		} else {			
			return $id;
		}
	}
	
	function searchCellRangeNumeric($numeric_value, $cell_range, $row_id, $column_id, $rows) {
		$in_range = true;
		$range_found = false;
	
		if (isset($cell_range['from_value'])) {
			if ($numeric_value < $cell_range['from_value']) $in_range = false;
			$range_found = true;								
		}
		if (isset($cell_range['to_value'])) {
			if ($numeric_value > $cell_range['to_value']) $in_range = false;
			$range_found = true;								
		}
		if (isset($cell_range['gt_value'])) {
			if ($numeric_value <= $cell_range['gt_value']) $in_range = false;
			$range_found = true;								
		}
		if (isset($cell_range['lt_value'])) {
			if ($numeric_value >= $cell_range['lt_value']) $in_range = false;
			$range_found = true;								
		}
		if (isset($cell_range['eq_value'])) {								
			if ($numeric_value != $cell_range['equal_value']) $in_range = false;
			$range_found = true;
		}
		if (isset($cell_range['min_range'])) {						
			$search_range = $this->parseRowColumnRange($cell_range['min_range'],$row_id,$column_id);
			if (isset($search_range)) {																				
				if (!isset($search_values['min'][$search_range['str']])) {
					$this->searchTableNumericValue('min',$search_range['str'],$search_range['row_from'],$search_range['row_to'],$search_range['column_from'],$search_range['column_to'],$rows,$search_values);
				} 
				$search_value = $search_values['min'][$search_range['str']];										
				if (is_numeric($search_value)) {
					if ($numeric_value != $search_value) $in_range = false;
				}	
			}
			$range_found = true;									
		}
		if (isset($cell_range['max_range'])) {
			$search_range = $this->parseRowColumnRange($cell_range['max_range'],$row_id,$column_id);
			if (isset($search_range)) {																				
				if (!isset($search_values['max'][$search_range['str']])) {
					$this->searchTableNumericValue('max',$search_range['str'],$search_range['row_from'],$search_range['row_to'],$search_range['column_from'],$search_range['column_to'],$rows,$search_values);
				} 
				$search_value = $search_values['max'][$search_range['str']];										
				if (is_numeric($search_value)) {
					if ($numeric_value != $search_value) $in_range = false;
				}	
			}	
			$range_found = true;								
		}
		if (isset($cell_range['median_range'])) {
			$search_range = $this->parseRowColumnRange($cell_range['median_range'],$row_id,$column_id);
			if (isset($search_range)) {																				
				if (!isset($search_values['med'][$search_range['str']])) {
					$this->searchTableNumericValue('med',$search_range['str'],$search_range['row_from'],$search_range['row_to'],$search_range['column_from'],$search_range['column_to'],$rows,$search_values);
				} 
				$search_value = $search_values['med'][$search_range['str']];										
				if (is_numeric($search_value)) {
					if ($numeric_value != $search_value) $in_range = false;
				}	
			}	
			$range_found = true;								
		}
		
		if ($in_range && $range_found) {
			return true;
		} else {
			return false;
		}

	}
	
	function searchCellRangeText($str_value, $cell_range) {
		$in_range = true;
		$range_found = false;

        $str_value_raw = $str_value;
        $str_value = strip_tags($str_value);
		$str_value_upper = mb_strtoupper($str_value);
		
		if (isset($cell_range['same_value'])) {
			if ($str_value != $cell_range['same_value']) $in_range = false;																
			$range_found = true;
		}
		if (isset($cell_range['samei_value'])) {						
			if ($str_value_upper != $cell_range['samei_value']) $in_range = false;									
			$range_found = true;
		}
		if (isset($cell_range['samen_value'])) {
			if ($str_value == $cell_range['samen_value']) $in_range = false;
			$range_found = true;
		}
		if (isset($cell_range['sameni_value'])) {						
			if ($str_value_upper == $cell_range['sameni_value']) $in_range = false;
			$range_found = true;
		}
		if (isset($cell_range['contain_value'])) {								
			if (mb_ereg($cell_range['contain_value'], $str_value)===false) $in_range = false;
			$range_found = true;
		}
		if (isset($cell_range['containi_value'])) {
			if (mb_eregi($cell_range['containi_value'], $str_value)===false) $in_range = false;								
			$range_found = true;
		}
		if (isset($cell_range['containn_value'])) {
			if (mb_ereg($cell_range['containn_value'], $str_value)!==false) $in_range = false;
			$range_found = true;
		}
		if (isset($cell_range['containni_value'])) {
			if (mb_eregi($cell_range['containni_value'], $str_value)!==false) $in_range = false;
			$range_found = true;
		}
        if (isset($cell_range['containt_value'])) {
            if (mb_eregi($cell_range['containt_value'], $str_value_raw)!==false) $in_range = false;
            $range_found = true;
        }
		
		if ($in_range && $range_found) {
			return true;
		} else {
			return false;
		}

	}
	
	function searchTableNumericValue($operation, $range_key, $row_from, $row_to, $column_from, $column_to, $rows, &$search_values) {				
		$value = null;
		
		$values = array();
		foreach ($rows as $row_id => $row) {
			foreach ($row as $column_id=> $cell) {
				if (!empty($row_from) && ($row_id<$row_from)) continue;
				if (!empty($row_to) && ($row_id>$row_to)) continue;
				if (!empty($column_from) && ($column_id<$column_from)) continue;
				if (!empty($column_to) && ($column_id>$column_to)) continue;
				
				$numeric_value = $this->is_my_numeric(trim($cell));
				if (is_numeric($numeric_value)) {
					$values[] = $numeric_value;
				}				
			}
		}
		
		if (!empty($values)) {
			switch ($operation) {
				case 'min':
					$value = min($values);
					break;
				case 'max':
					$value = max($values);
					break;
				case 'med':
					sort($values, SORT_NUMERIC);
					$median_index = round(count($values) /2, 0, PHP_ROUND_HALF_DOWN);	
					$value = $values[$median_index];
					break;		
			}
		}
		
		if (!isset($search_values[$operation])) $search_values[$operation] = array();
		$search_values[$operation][$range_key] = $value;
	}
	
	function parseCellRangeStr($range, &$cell_ids, &$cell_ranges) {
		
		$range_raw = $range;						
		$range = strtoupper(str_replace(' ','', trim($range)));				
		# <> are not allowed in the XML files, so replace them with LT and GT
		$gl_values = array('LTE','LT','GTE','GT');
		$sy_values = array('<=','<','>=','>',);									

		$range = str_replace($gl_values, $sy_values, $range);	
		
		if (preg_match( '/^([\d]+),([\d]+)$/', $range, $matches)) {
			$from = intval($matches[1]);
			$to = intval($matches[2]);
			$cell_ids[] = array('row_id'=>$from, 'column_id' =>$to);
		} else if (preg_match( '/^([-+]?[0-9]*\.?[0-9]+)-([-+]?[0-9]*\.?[0-9]+)$/', $range, $matches)) {
			$from = floatval($matches[1]);
			$to = floatval($matches[2]);
			$cell_ranges[] = array('from_value'=>$from, 'to_value' =>$to);
		} else if (preg_match( '/^>=([-+]?[0-9]*\.?[0-9]+)$/', $range, $matches)) {
			$from = floatval($matches[1]);					
			$cell_ranges[] = array('from_value'=>$from);				
		} else if (preg_match( '/^<=([-+]?[0-9]*\.?[0-9]+)$/', $range, $matches)) {
			$to = floatval($matches[1]);
			$cell_ranges[] = array('to_value' =>$to);
		} else if (preg_match( '/^=([-+]?[0-9]*\.?[0-9]+)$/', $range, $matches)) {
			$value = floatval($matches[1]);					
			$cell_ranges[] = array('eq_value'=>$value);
		}  else if (preg_match( '/^>([-+]?[0-9]*\.?[0-9]+)$/', $range, $matches)) {
			$from = floatval($matches[1]);									
			$cell_ranges[] = array('gt_value'=>$from);				
		} else if (preg_match( '/^<([-+]?[0-9]*\.?[0-9]+)$/', $range, $matches)) {				
			$to = floatval($matches[1]);
			$cell_ranges[] = array('lt_value' =>$to);
		} else if (preg_match( '/^SAMEI(.+)$/i', $range_raw, $matches)) {
			$value = mb_strtoupper(trim($matches[1]));
			$cell_ranges[] = array('samei_value'=>$value);				
		} else if (preg_match( '/^SAMENI(.+)$/i', $range_raw, $matches)) {
			$value = mb_strtoupper(trim($matches[1]));
			$cell_ranges[] = array('sameni_value'=>$value);				
		} else if (preg_match( '/^SAMEN(.+)$/i', $range_raw, $matches)) {
			$value = mb_strtoupper(trim($matches[1]));
			$cell_ranges[] = array('samen_value'=>$value);				
		} else if (preg_match( '/^SAME(.+)$/i', $range_raw, $matches)) {
			$value = trim($matches[1]);
			$cell_ranges[] = array('same_value'=>$value);
		} else if (preg_match( '/^CONTAINI(.+)$/i', $range_raw, $matches)) {
			$value = trim($matches[1]);
			$cell_ranges[] = array('containi_value'=>$value);
		} else if (preg_match( '/^CONTAINNI(.+)$/i', $range_raw, $matches)) {
			$value = trim($matches[1]);
			$cell_ranges[] = array('containni_value'=>$value);
		} else if (preg_match( '/^CONTAINN(.+)$/i', $range_raw, $matches)) {
			$value = trim($matches[1]);
			$cell_ranges[] = array('containn_value'=>$value);
		} else if (preg_match( '/^CONTAINT(.+)$/i', $range_raw, $matches)) {
            $value = trim($matches[1]);
            $cell_ranges[] = array('containt_value'=>$value);
        } else if (preg_match( '/^CONTAIN(.+)$/i', $range_raw, $matches)) {
			$value = trim($matches[1]);
			$cell_ranges[] = array('contain_value'=>$value);
		} else if (preg_match( '/^MIN(.+)$/i', $range_raw, $matches)) {
			$value = trim($matches[1]);
			if ($value) $cell_ranges[] = array('min_range'=>$value);
		} else if (preg_match( '/^MAX(.+)$/i', $range_raw, $matches)) {
			$value = trim($matches[1]);
			if ($value) $cell_ranges[] = array('max_range'=>$value);
		}  else if (preg_match( '/^MEDIAN(.+)$/i', $range_raw, $matches)) {
			$value = trim($matches[1]);
			if ($value) $cell_ranges[] = array('median_range'=>$value);
		}															

	}
	
	function getCellRangeIds($range, &$rows) {
		$range_ids = array();
				
		$cell_ranges = array();
		$cell_ids = array();
	
		$range = trim($range);		
		if (!empty($range)) {
			$subranges = explode(';',$range);
			foreach ($subranges as $subrange) {
				$this->parseCellRangeStr($subrange, $cell_ids, $cell_ranges);
			}
		}
		
		if (!empty($cell_ids)) {
			foreach ($cell_ids as $cell_id) {
				$range_ids[] = $cell_id['row_id'] . ','. $cell_id['column_id'];
			}						
		}				
		
		if (!empty($cell_ranges)) {		
			foreach ($rows as $row_id => $row) {
				foreach ($row as $column_id => $cell) {				
					// numeric comparisons
					$numeric_value = $this->is_my_numeric(trim($cell));
					if (is_numeric($numeric_value)) {						
						foreach ($cell_ranges as $cell_range) {						
							$in_range = $this->searchCellRangeNumeric($numeric_value, $cell_range, $row_id, $column_id, $rows);
							if ($in_range) {
								$range_ids[] = $row_id . ','. $column_id;
							}														
						}
					} 
					
					// text comparisons
					$str_value = trim($cell);																			
					foreach ($cell_ranges as $cell_range) {					
						$in_range = $this->searchCellRangeText($str_value, $cell_range);															
						if ($in_range) {
							$range_ids[] = $row_id . ','. $column_id;
						}						
					}
				}
			}
		} // cell ranges
		
		return array_unique($range_ids);		
	}		
		
	function getCellFormatted($format, $value) {
			
		if (empty($value)) return '';				
	
		list($data_type,$data_type_params) = explode(DATA_TYPE_SEPARATOR,$format);
		$data_type_params = htmlspecialchars_decode($data_type_params,ENT_QUOTES);
		
		switch ($data_type) {
			case DATA_TYPE_TEXT: 					
				$text_format = $data_type_params;				
				switch ($text_format) {
					case FORMAT_TEXT_UPPERCASE: $value = mb_strtoupper($value); break;
					case FORMAT_TEXT_LOWERCASE: $value = mb_strtolower($value); break;
					case FORMAT_TEXT_UCFIRST: $value = my_ucfirst($value); break;
					case FORMAT_TEXT_UCWORDS: $value = my_ucwords($value); break;	
				}				
				break;
			case DATA_TYPE_NUMERIC: 			
				$numeric_value = $this->is_my_numeric(trim($value));
				if (is_numeric($numeric_value)) {
					list($numeric_decimals, $numeric_dec_point, $numeric_thousands_sep) = explode(FORMAT_NUMERIC_SEPARATOR, $data_type_params);				
					$value = number_format ( $numeric_value , $numeric_decimals , getNumericPoint($numeric_dec_point) , getNumericPoint($numeric_thousands_sep) );
				}
				break;
			case DATA_TYPE_CURRENCY: 			
				list($currency_decimals, $currency_dec_point, $currency_thousands_sep, $currency_symbol, $currency_symbol_order) = explode(FORMAT_CURRENCY_SEPARATOR, $data_type_params);
				if (!empty($currency_symbol)) {
					$numeric_value = $this->is_my_numeric(trim(str_replace($currency_symbol, '', trim($value))));				
				} else {
					$numeric_value = $this->is_my_numeric(trim($value));				
				}	
				if (is_numeric($numeric_value)) {													
					$value = number_format ( $numeric_value , $currency_decimals , getNumericPoint($currency_dec_point), getNumericPoint($currency_thousands_sep) );
					switch ($currency_symbol_order) {
						case FORMAT_CURRENCY_SYMBOL_ORDER_NONE: break;
						case FORMAT_CURRENCY_SYMBOL_ORDER_BEFORE: $value = $currency_symbol . $value; break;
						case FORMAT_CURRENCY_SYMBOL_ORDER_AFTER: $value = $value . $currency_symbol; break;
					}
				}
				break;
			case DATA_TYPE_DATE: 
				$timestamp_value = strtotime(trim($value));
				if ($timestamp_value && ($timestamp_value>0)) {
					$date_format = $data_type_params;										
					switch ($date_format) {
						case FORMAT_DATE_DDMMYY_1: $date_format = "d-m-y"; $value = date($date_format, $timestamp_value); break; // 21-10-09
						case FORMAT_DATE_DDMMYY_2: $date_format = "d/m/y"; $value = date($date_format, $timestamp_value); break; // 21/10/09
						case FORMAT_DATE_MMDDYY_1: $date_format = "m-d-y"; $value = date($date_format, $timestamp_value); break; // 10-21-09
						case FORMAT_DATE_MMDDYY_2: $date_format = "m/d/y"; $value = date($date_format, $timestamp_value); break; // 10/21/09
						case FORMAT_DATE_DDMMYYYY_1: $date_format = "d-m-Y"; $value = date($date_format, $timestamp_value); break; // 21-10-2009
						case FORMAT_DATE_DDMMYYYY_2: $date_format = "d/m/Y"; $value = date($date_format, $timestamp_value); break; // 21/10/2009
						case FORMAT_DATE_DDMMYYYY_3: $date_format = "d/M/Y"; $value = date($date_format, $timestamp_value); break; // 21/Oct/2009
						case FORMAT_DATE_MMDDYYYY_1: $date_format = "m-d-Y"; $value = date($date_format, $timestamp_value); break; // 10-21-2009
						case FORMAT_DATE_MMDDYYYY_2: $date_format = "m/d/Y"; $value = date($date_format, $timestamp_value); break; // 10/21/2009
						case FORMAT_DATE_MMDDYYYY_3: $date_format = "M j, Y"; $value = date($date_format, $timestamp_value); break; // Jan 1, 2009
						case FORMAT_DATE_dDDMMYYYY_1: $date_format = "l, d-M-Y"; $value = date($date_format, $timestamp_value); break; // Monday, 21-Oct-2009
						case FORMAT_DATE_dDDMMYYYY_2: $date_format = "l, d/M/Y"; $value = date($date_format, $timestamp_value); break; // Monday, 21/Oct/2009
						case FORMAT_DATE_dMMDDYYYY_1: $date_format = "l, M j,Y"; $value = date($date_format, $timestamp_value); break; // Monday, Oct 1, 2009
						case FORMAT_DATE_dMMDDYYYY_2: $date_format = "l, d-M-Y"; $value = date($date_format, $timestamp_value); break; // Monday, 21-Oct-2009
						case FORMAT_DATE_dDDm_1: $date_format = "l, j M"; $value = date($date_format, $timestamp_value); break; // Monday, 21 October
						case FORMAT_DATE_dmDD_1: $date_format = "l, M j"; $value = date($date_format, $timestamp_value); break; // Monday, October 21
					}
				}
				break;
		}				
		
		return $value;
	}
	
	function getCellReplaced($replacement, $value) {
				
		if (empty($value)) return '';				
	
		list($case_sensitive,$replace_from,$replace_to) = explode(REPLACEMENT_SEPARATOR,$replacement,3);
		$replace_from = htmlspecialchars_decode($replace_from,ENT_QUOTES);
		$replace_to = htmlspecialchars_decode($replace_to,ENT_QUOTES);				
		
		if (!empty($replace_from)) {			
			if ($case_sensitive) {
				$value = mb_ereg_replace ( $replace_from,$replace_to,$value);
			} else {
				$value = mb_eregi_replace ( $replace_from,$replace_to,$value);
			}	
		}
		
		return $value;
	}
	
	function getFormattedTable($rows, $ruleset) {				
	
		if (!empty($table_class_suffix)) $table_class_suffix = '-' . $table_class_suffix; else $table_class_suffix = '';							
		
		$num_of_rows = count($rows);
		$num_of_columns = count($rows[1]);		
		
		// Calculation pre-process
		foreach ($rows as $row_id => $row) {
			foreach ($row as $column_id => $column) {								
				// calculation
				$calculation = null;
				if (isset($ruleset['rows'][$row_id]['calculation'])) $calculation = $ruleset['rows'][$row_id]['calculation'];
				if (isset($ruleset['columns'][$column_id]['calculation'])) $calculation = $ruleset['columns'][$column_id]['calculation'];
				if (isset($ruleset['cells'][$row_id][$column_id]['calculation'])) $calculation = $ruleset['cells'][$row_id][$column_id]['calculation'];				
				if (!empty($calculation)) {
					$calculation_value = $this->getCellCalculation($calculation, $row_id, $column_id, $rows);
					if (!is_null($calculation_value)) $rows[$row_id][$column_id] = $calculation_value;
				}				
			}
		}									
		
		// Autospan pre-process (colspan)
		for ($row_id = 1; $row_id <= $num_of_rows; $row_id++) {
			if (isset($ruleset['rows'][$row_id]['autospan'])) {
				$row_start = null;
				$column_start = null;
				$colspan = 1;								
				
				for ($column_id = 1; $column_id <= $num_of_columns; $column_id++) {			
					$cell = trim($rows[$row_id][$column_id]);																							
					if (empty($cell)) {						
						$ruleset['cells'][$row_id][$column_id]['autospan'] = 'IGNORE';
						$colspan++;													
					} else {						
						if (isset($row_start)) {
							if ($colspan > 1) {
								$ruleset['cells'][$row_start][$column_start]['autospan'] = 'colspan="'.$colspan.'"';							
								$colspan = 1;
							} else {
								$ruleset['cells'][$row_start][$column_start]['autospan'] = '';
							}
						} 						
						$row_start = $row_id;
						$column_start = $column_id;												
					}					
				}
				
				if (isset($row_start)) {
					if ($colspan > 1) {
						$ruleset['cells'][$row_start][$column_start]['autospan'] = 'colspan="'.$colspan.'"';																					
					} else {
						$ruleset['cells'][$row_start][$column_start]['autospan'] = '';
					}	
				}
				
			}						
		}
		
		// Autospan pre-process (rowspan)
		for ($column_id = 1; $column_id <= $num_of_columns; $column_id++) {
			if (isset($ruleset['columns'][$column_id]['autospan'])) {
				$row_start = null;
				$column_start = null;
				$rowspan = 1;
				for ($row_id = 1; $row_id <= $num_of_rows; $row_id++) {
					$cell = trim($rows[$row_id][$column_id]);
					// ignore cells that have been colspaned
					if (!empty($ruleset['cells'][$row_id][$column_id]['autospan'])) continue;					
					
					if (empty($cell)) {						
						$ruleset['cells'][$row_id][$column_id]['autospan'] = 'IGNORE';
						$rowspan++;						
					} else {						
						if (isset($row_start)) {
							if ($rowspan > 1) {
								$ruleset['cells'][$row_start][$column_start]['autospan'] = 'rowspan="'.$rowspan.'"';
								$rowspan = 1;
							}														
						}						
						$row_start = $row_id;
						$column_start = $column_id;						
					}					
				}
				
				if (isset($row_start)) {
					if ($rowspan > 1) {
						$ruleset['cells'][$row_start][$column_start]['autospan'] = 'rowspan="'.$rowspan.'"';
					} else {
						$ruleset['cells'][$row_start][$column_start]['autospan'] = '';
					}
				}
			}
		}	
							
		if ($ruleset['style']) { 
			$ruleset_style_prefix = sprintf(TABULIZER_INCLUDE_STYLE_PREFIX,$ruleset['style']) . "\n"; 			
			if ($this->use_comments) {
				$ruleset_style_prefix = '<!--'.$ruleset_style_prefix.'-->';
			}
		} else {
			$ruleset_style_prefix = ''; 
		}			
						
		$html = $ruleset_style_prefix . '<table class="tabtable'.$ruleset['suffix'].'">';
		foreach ($rows as $row_id => $row) {
			if (isset($ruleset['rows'][$row_id]['style'])) $row_class_append = $ruleset['rows'][$row_id]['style']; else $row_class_append = '';
			$html .= '<tr class="tabrow '.$row_class_append.'">';
									
			foreach ($row as $column_id => $column) {
				
				// autospan
				if (isset($ruleset['cells'][$row_id][$column_id]['autospan'])) {
					$autospan = $ruleset['cells'][$row_id][$column_id]['autospan'];
					if ($autospan =='IGNORE') {
						// ignore cell
						continue;
					} else {
						$column_autospan = $autospan;
					}
				} else $column_autospan = '';
				
				// css style
				$column_class_append = '';	
				if (isset($ruleset['columns'][$column_id]['style'])) $column_class_append .= ' ' . $ruleset['columns'][$column_id]['style'];
				if (isset($ruleset['cells'][$row_id][$column_id]['style'])) $column_class_append .= ' ' . $ruleset['cells'][$row_id][$column_id]['style'];								
				
				// Replacement
				$replacements = null;
				if (isset($ruleset['rows'][$row_id]['replacement'])) $replacements = $ruleset['rows'][$row_id]['replacement'];
				if (isset($ruleset['columns'][$column_id]['replacement'])) $replacements = $ruleset['columns'][$column_id]['replacement'];
				if (isset($ruleset['cells'][$row_id][$column_id]['replacement'])) $replacements = $ruleset['cells'][$row_id][$column_id]['replacement'];
				if (!empty($replacements)) {					
					foreach ($replacements as $replacement) {
						$column = $this->getCellReplaced($replacement, $column);					
					}
				}												
				
				// format
				$format = null;
				if (isset($ruleset['rows'][$row_id]['format'])) $format = $ruleset['rows'][$row_id]['format'];
				if (isset($ruleset['columns'][$column_id]['format'])) $format = $ruleset['columns'][$column_id]['format'];
				if (isset($ruleset['cells'][$row_id][$column_id]['format'])) $format = $ruleset['cells'][$row_id][$column_id]['format'];
				if (!empty($format)) {					
					$column = $this->getCellFormatted($format, $column);					
				}								
				
				// put it all together
				$html .= '<td '.$column_autospan.' class="tabcol '.$column_class_append.'">'. $column . '</td>';
			}
			
			$html .= "</tr>\n";													
		}
		$html .= "</table>";
		
		
		return $html;
	}
	
	function process($text, $separator, $enclosure, $ruleset_filename, $ruleset_name, &$errors) {
		$rows = array();						
		$errors = array();
		$table_html = null;							
		
		$separator = $this->getSeparatorChar($separator, $errors);
		$enclosure = $this->getEnclosureChar($enclosure, $errors);
		
		$this->parseInput($text, $separator, $enclosure, $rows);
		if ($this->validateTableRows($rows, $errors)) {
			$ruleset = $this->loadRuleset($ruleset_filename, $ruleset_name, $rows, $errors);								
			if (!empty($ruleset)) {												
				$table_html = $this->getFormattedTable($rows, $ruleset);							
			}
		}				
				
		if (!empty($errors)) {
			return null;
		} else {						
			return $table_html;
		}	
	}
	
	function getSeparatorChar($code, &$errors) {
		switch ($code) {
			case SEPARATOR_COMMA: $char = ','; break;
			case SEPARATOR_SEMICOLON: $char = ';'; break;
			case SEPARATOR_SPACE: $char = ' '; break;
			case SEPARATOR_TAB: $char = '	'; break;
			case SEPARATOR_ASTERISK: $char = '*'; break;
			case SEPARATOR_CARET: $char = '^'; break;
			default: $errors[] = sprintf(JText::_('COM_TABULIZER_INVALID_SEPARATOR'),$code); $char = ','; break;
		}
		return $char;
	}
	
	function getEnclosureChar($code, &$errors) {
		switch ($code) {
			case ENCLOSURE_DOUBLE_QUOTES: $char = '"'; break;			
			case ENCLOSURE_SINGLE_QUOTES: $char = '\''; break;
			case ENCLOSURE_NONE: 			
			default: $char = ''; break;
		}
		return $char;
	}
	
	function loadRuleset($filename, $name, $rows, &$errors) {
		# init ruleset
		$ruleset = array('table'=>array(), 'rows'=>array(), 'columns' => array(), 'cells' => array(), 'suffix'=> '', 'style'=> '');
		
		$num_of_rows = count($rows);
		$num_of_columns = count($rows[1]);
		
		$autospan_table = null; // this is used in combination with range_key		
		
		if ($name == 'none') {
			return $ruleset;
		}				
	
		# load all xml files in rules folder
		$dir = $this->rules_dir;				
		
		$xmlfiles = array();
		if (empty($filename)) {
			# load all xml files in rules folder					
			if (is_dir($dir)) {
				if ($dh = opendir($dir)) {
					while (($file = readdir($dh)) !== false) {
						if (is_file($dir . $file)) {
							if (preg_match('/'.XML_FILE_EXT.'$/i',$file)) {
								$xmlfiles[] = $file;
							}
						}
					}	
					closedir($dh);
				}			
			}
		} else {
			# load sepcific xml file
			if (file_exists($dir . $filename)) {
				$xmlfiles[] = $filename;
			}
		}												
		
		if (empty($xmlfiles)) {
			$errors[] = sprintf(JText::_('COM_TABULIZER_RULESET_FOLDER_EMPTY'),$dir);
			return null;
		}								
			
		# traverse each file to locate selected rule
		$ruleset_found = false;		
		foreach ($xmlfiles as $xmlfile) {							
			$contents = file_get_contents($dir . $xmlfile);
			if (empty($contents)) continue;
			
			$p = xml_parser_create();
			xml_parse_into_struct($p, $contents, $vals, $index);
			xml_parser_free($p);						
						
			$ruleset_open = false;
			$class_suffix = '';
			$ruleset_style = '';						
			
			foreach ($vals as $node) {
				switch ($node['tag']) {
					case 'RULES':
						if (!empty($node['attributes']['STYLE'])) {
							$ruleset_style = trim($node['attributes']['STYLE']);
						}
						break;
									
					case 'RULESET':
						if ($node['type'] == 'open') {						
							if ($node['attributes']['NAME'] == $name) {																													
								$ruleset_found = true;
								$ruleset_open = true;
								if (!empty($node['attributes']['SUFFIX'])) {
									$class_suffix = trim($node['attributes']['SUFFIX']);
									if (!empty($class_suffix)) $class_suffix = '-'.$class_suffix;
								}
								if (!empty($node['attributes']['STYLE'])) {
									$ruleset_style = trim($node['attributes']['STYLE']);
								}								
							}
						} else if ($node['type'] == 'close') {
							if ($ruleset_open) $ruleset_open = false;
						} else if ($node['type'] == 'complete') {	
							if ($node['attributes']['NAME'] == $name) {
								$ruleset_found = true;
								if (!empty($node['attributes']['SUFFIX'])) {
									$class_suffix = trim($node['attributes']['SUFFIX']);
									if (!empty($class_suffix)) $class_suffix = '-'.$class_suffix;
								}
								if (!empty($node['attributes']['STYLE'])) {
									$ruleset_style = trim($node['attributes']['STYLE']);
								}
							}
						}
						break;
										
					case 'RULE':
						if ($ruleset_open) {										
							if ($node['type'] == 'complete') {							
								$attributes = $node['attributes'];
								$element = empty($attributes['ELEMENT'])?null:strtolower(trim($attributes['ELEMENT']));
								if (!empty($element)) {
									// verify element is acceptable
									$acceptable_elements = array('table','row','column','cell');
									if (!in_array($element, $acceptable_elements)) $element = null;
								}								
								$range =  empty($attributes['RANGE'])?null:trim($attributes['RANGE']);
								// keep RANGE_COUNT for backward compatibility
								$range_key = empty($attributes['RANGE_COUNT'])?0:intval(trim($attributes['RANGE_COUNT']));
								$range_key = empty($attributes['RANGE_KEY'])?$range_key:intval(trim($attributes['RANGE_KEY']));								
								$set_attributes = array();
								if (isset($attributes['ELEMENT_SECTION'])) $set_attributes['element_section'] = $attributes['ELEMENT_SECTION'];
								if (isset($attributes['STYLE'])) $set_attributes['style'] = $attributes['STYLE'];
								if (isset($attributes['AUTOSPAN'])) $set_attributes['autospan'] =  $attributes['AUTOSPAN'];
								if (isset($attributes['FORMAT'])) $set_attributes['format'] =  $attributes['FORMAT'];
								if (isset($attributes['REPLACEMENT'])) $set_attributes['replacement'] =  $attributes['REPLACEMENT'];
								if (isset($attributes['CALCULATION'])) $set_attributes['calculation'] =  $attributes['CALCULATION'];								
								if (!empty($element) && !empty($range) && !empty($set_attributes)) {
									$area = ($element == 'table')?$element:$element.'s';
									if (!isset($ruleset[$area][$range])) {
                    					$ruleset[$area][$range] = array($range_key => array());                    										
									} 
									if (!isset($ruleset[$area][$range][$range_key])) {
					                    $ruleset[$area][$range][$range_key] = array();
									}
									$ruleset_entry = &$ruleset[$area][$range][$range_key];
									foreach ($set_attributes as $attr_name => $attr_value) {										
										switch ($attr_name) {										
											case 'style': 
												if (!isset($ruleset_entry[$attr_name])) { $ruleset_entry[$attr_name] = $attr_value; } else { $ruleset_entry[$attr_name] .= ' ' . $attr_value; } 
												break;																							
											case 'replacement':											
												if (!isset($ruleset_entry[$attr_name])) { $ruleset_entry[$attr_name] = array($attr_value); } else { $ruleset_entry[$attr_name][] = $attr_value; } 
												break;
											case 'autospan': 
												$ruleset_entry[$attr_name] = 1; 
												break;																																														
											case 'format': 
											case 'calculation':											
												$ruleset_entry[$attr_name] = $attr_value; 
												break;
											case 'element_section': 
												if ($element == 'row') $last = $num_of_columns; 
												else if ($element == 'column') $last = $num_of_rows; 
												else $last = 0;	// ignore element section for elements "table" and "cell"											
												$section_ids = $this->getRangeIds($attr_value, 0, $last, null, null);																																																																								
												if (!isset($ruleset_entry[$attr_name])) { $ruleset_entry[$attr_name] = $section_ids; } else { $ruleset_entry[$attr_name] = array_intersect($ruleset_entry[$attr_name],$section_ids); }
												break;																																										
										}
									}
								}
							}
						}	
						break;								 
				}
			}
			
			if ($ruleset_found) break;
		
		} // foreach xml files
		
		if (!$ruleset_found) {
			$errors[] = sprintf(JText::_('COM_TABULIZER_RULESET_NOT_FOUND'),$name);
			return null;
		}
		
		// now convert ranges to actual ids
				
		$rs = array('table'=> array(), 'rows' => array(), 'columns' => array(), 'cells'=> array(), 'suffix' => $class_suffix, 'style' => $ruleset_style );
		
		// process table
		$area = 'table';										
		$rs[$area] = array();
		// to do ...
		
		// process rows and columns
		$areas = array('rows', 'columns');
		foreach ($areas as $area) {				
			$last_var = 'num_of_' . $area;
			$last = $$last_var;
						
			foreach ($ruleset[$area] as $range => $range_rule) {
				foreach ($range_rule as $range_key => $rule) {															
																					
					if ($range_key && empty($autospan_table)) {
						$autospan_table = array('rows' => array(), 'columns' => array());
						$this->createAutospanTable($rows, $autospan_table);
					}															
					
					$ids = $this->getRangeIds($range, $range_key, $last, $area, $autospan_table);													
										
					// if there is an element section, we need to transfer the row/column section to the "cells" area
					if (!empty($rule['element_section'])) {
						$section_ids = $rule['element_section'];
						if ($area == 'rows') {
							foreach ($ids as $id) {
								foreach ($section_ids as $section_id) {
									$cell_range = "{$id},{$section_id}";
									if (!isset($ruleset['cells'][$cell_range])) {
                    					$ruleset['cells'][$cell_range] = array(0 => array());                    										
									} 
									if (!isset($ruleset['cells'][$cell_range][0])) {
					                    $ruleset['cells'][$cell_range][0] = array();
									}
									$ruleset['cells'][$cell_range][0] = $rule;
								}
							}
						} else if ($area == 'columns') {
							foreach ($ids as $id) {
								foreach ($section_ids as $section_id) {
									$cell_range = "{$section_id},{$id}";
									if (!isset($ruleset['cells'][$cell_range])) {
                    					$ruleset['cells'][$cell_range] = array(0 => array());                    										
									} 
									if (!isset($ruleset['cells'][$cell_range][0])) {
					                    $ruleset['cells'][$cell_range][0] = array();
									}
									$ruleset['cells'][$cell_range][0] = $rule;
								}
							}
						} 
					} else {											
						foreach ($ids as $id) {
							if (!isset($rs[$area][$id])) {
								$rs[$area][$id] = array();
								foreach ($rule as $key => $value) {
									$rs[$area][$id][$key] = $value;
								}
							} else {
								foreach ($rule as $key => $value) {
									if ($key == 'style') {
										if (isset($rs[$area][$id][$key])) $rs[$area][$id][$key] .= ' ' . $value;
										else $rs[$area][$id][$key] = $value;
									} else if ($key == 'replacement') {								
										if (isset($rs[$area][$id][$key])) $rs[$area][$id][$key] = array_unique(array_merge($value, $rs[$area][$id][$key]));
										else $rs[$area][$id][$key] = $value;
									} else {
										$rs[$area][$id][$key] = $value;
									}	
								}
							}									
						}						
					}
				
				} // foreach $range_rule								
				
			}
		}			
		
		// process cells
		$area = 'cells';										
		$rs[$area] = array();
		
		$search_values = array();								
				
		foreach ($ruleset[$area] as $range => $range_rule) {
			foreach ($range_rule as $range_key => $rule) {
														
				$ids = $this->getCellRangeIds($range, $rows);
				if (!empty($ids)) {
					foreach ($ids as $id) {
						list($row_id,$column_id) = explode(',',$id);
						if (!isset($rs[$area][$row_id][$column_id])) {
							$rs[$area][$row_id][$column_id] = array();
							foreach ($rule as $key => $value) {
								$rs[$area][$row_id][$column_id][$key] = $value;
							}
						} else {
							foreach ($rule as $key => $value) {
								if ($key == 'style') {
									if (isset($rs[$area][$row_id][$column_id][$key])) $rs[$area][$row_id][$column_id][$key] .= ' ' . $value;
									else $rs[$area][$row_id][$column_id][$key] = $value;
								} else if ($key == 'replacement') {
									if (isset($rs[$area][$row_id][$column_id][$key])) $rs[$area][$row_id][$column_id][$key] = array_unique(array_merge($value,$rs[$area][$row_id][$column_id][$key]));
									else $rs[$area][$row_id][$column_id][$key] = $value;
								} else {
									$rs[$area][$row_id][$column_id][$key] = $value;
								}	
							}																		
						}								
					}						
				}						
							
			} //  foreach $range_rule	

		} // foreach $ruleset[$area] (area is cells)	
		
		return $rs;							
	}
	
	/**
	* Create a sample table to preview rules
	*/	
	function previewRuleset($ruleset_filename, $ruleset_name, &$html, &$errors, $sample_data_text = null, $sample_data_sep = 'cm', $sample_data_enc = 'none') {
		
		$table_text = '';
		
		if (!empty($sample_data_text)) {			
			$table_text = $sample_data_text;
			$separator = $sample_data_sep;
			$enclosure = $sample_data_enc;
		} else {
			$num_of_rows = 10;
			$num_of_columns = 5;
			
			$separator = 'cm'; // comma
			$enclosure = 'none';	
			$sep = ',';			
			for ($i=1;$i<=$num_of_rows;$i++) {
				for ($j=1;$j<=$num_of_columns;$j++) {			
					if ($i==1) $cell = JText::_('COM_TABULIZER_COLUMN').' '. $j;
					else if ($j ==1) $cell = JText::_('COM_TABULIZER_ROW').' '.$i; 
					else $cell = 10 - mt_rand(0,20);
					if ($j<$num_of_columns) $cell .= $sep;
					$table_text .= $cell;
					
				}
				$table_text .= "\n";
			}
		}		
		
		$this->setComments(true);
		$html = $this->process($table_text, $separator, $enclosure, $ruleset_filename, $ruleset_name, $errors);
		if (!empty($errors)) {		
			return false;
		} else {				
			// remove include style/CSS directive
			$pattern = TABULIZER_INCLUDE_STYLE_REGEX;
			$replacement = '';											

			while (preg_match($pattern, $html, $regs, PREG_OFFSET_CAPTURE)) {						
				$html = substr_replace($html, $replacement, $regs[0][1], strlen($regs[0][0]));			
			}				
			
			return true;
		}		
	
	} // end of previewRules
	
}

?>