shop.balmet.com

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README

spreadsheetreader_ods.php (7689B)


      1 <?php
      2 /**
      3  * Class for parsing ODS files
      4  *
      5  * @author Martins Pilsetnieks
      6  */
      7 class SpreadsheetReader_ODS implements Iterator, Countable {
      8 	private $Options = array(
      9 		'TempDir' => '',
     10 		'ReturnDateTimeObjects' => false
     11 	);
     12 
     13 	/**
     14 	 * @var string Path to temporary content file
     15 	 */
     16 	private $ContentPath = '';
     17 	/**
     18 	 * @var XMLReader XML reader object
     19 	 */
     20 	private $Content = false;
     21 
     22 	/**
     23 	 * @var array Data about separate sheets in the file
     24 	 */
     25 	private $Sheets = false;
     26 
     27 	private $CurrentRow = null;
     28 
     29 	/**
     30 	 * @var int Number of the sheet we're currently reading
     31 	 */
     32 	private $CurrentSheet = 0;
     33 
     34 	private $Index = 0;
     35 
     36 	private $TableOpen = false;
     37 	private $RowOpen = false;
     38 
     39 	/**
     40 	 * @param string Path to file
     41 	 * @param array Options:
     42 	 *	TempDir => string Temporary directory path
     43 	 *	ReturnDateTimeObjects => bool True => dates and times will be returned as PHP DateTime objects, false => as strings
     44 	 */
     45 	public function __construct($Filepath, array $Options = null)
     46 	{
     47 		if (!is_readable($Filepath))
     48 		{
     49 		throw new Exception('SpreadsheetReader_ODS: File not readable ('.$Filepath.')');
     50 		}
     51 
     52 		$this -> TempDir = isset($Options['TempDir']) && is_writable($Options['TempDir']) ?
     53 		$Options['TempDir'] :
     54 		sys_get_temp_dir();
     55 
     56 		$this -> TempDir = rtrim($this -> TempDir, DIRECTORY_SEPARATOR);
     57 		$this -> TempDir = $this -> TempDir.DIRECTORY_SEPARATOR.uniqid().DIRECTORY_SEPARATOR;
     58 
     59 		$Zip = new ZipArchive;
     60 		$Status = $Zip -> open($Filepath);
     61 
     62 		if ($Status !== true)
     63 		{
     64 		throw new Exception('SpreadsheetReader_ODS: File not readable ('.$Filepath.') (Error '.$Status.')');
     65 		}
     66 
     67 		if ($Zip -> locateName('content.xml') !== false)
     68 		{
     69 		$Zip -> extractTo($this -> TempDir, 'content.xml');
     70 		$this -> ContentPath = $this -> TempDir.'content.xml';
     71 		}
     72 
     73 		$Zip -> close();
     74 
     75 		if ($this -> ContentPath && is_readable($this -> ContentPath))
     76 		{
     77 		$this -> Content = new XMLReader;
     78 		$this -> Content -> open($this -> ContentPath);
     79 		$this -> Valid = true;
     80 		}
     81 	}
     82 
     83 	/**
     84 	 * Destructor, destroys all that remains (closes and deletes temp files)
     85 	 */
     86 	public function __destruct()
     87 	{
     88 		if ($this -> Content && $this -> Content instanceof XMLReader)
     89 		{
     90 		$this -> Content -> close();
     91 		unset($this -> Content);
     92 		}
     93 		if (file_exists($this -> ContentPath))
     94 		{
     95 		@unlink($this -> ContentPath);
     96 		unset($this -> ContentPath);
     97 		}
     98 	}
     99 
    100 	/**
    101 	 * Retrieves an array with information about sheets in the current file
    102 	 *
    103 	 * @return array List of sheets (key is sheet index, value is name)
    104 	 */
    105 	public function Sheets()
    106 	{
    107 		if ($this -> Sheets === false)
    108 		{
    109 		$this -> Sheets = array();
    110 
    111 		if ($this -> Valid)
    112 		{
    113 			$this -> SheetReader = new XMLReader;
    114 			$this -> SheetReader -> open($this -> ContentPath);
    115 
    116 			while ($this -> SheetReader -> read())
    117 			{
    118 			if ($this -> SheetReader -> name == 'table:table')
    119 			{
    120 				$this -> Sheets[] = $this -> SheetReader -> getAttribute('table:name');
    121 				$this -> SheetReader -> next();
    122 			}
    123 			}
    124 			
    125 			$this -> SheetReader -> close();
    126 		}
    127 		}
    128 		return $this -> Sheets;
    129 	}
    130 
    131 	/**
    132 	 * Changes the current sheet in the file to another
    133 	 *
    134 	 * @param int Sheet index
    135 	 *
    136 	 * @return bool True if sheet was successfully changed, false otherwise.
    137 	 */
    138 	public function ChangeSheet($Index)
    139 	{
    140 		$Index = (int)$Index;
    141 
    142 		$Sheets = $this -> Sheets();
    143 		if (isset($Sheets[$Index]))
    144 		{
    145 		$this -> CurrentSheet = $Index;
    146 		$this -> rewind();
    147 
    148 		return true;
    149 		}
    150 
    151 		return false;
    152 	}
    153 
    154 	// !Iterator interface methods
    155 	/** 
    156 	 * Rewind the Iterator to the first element.
    157 	 * Similar to the reset() function for arrays in PHP
    158 	 */ 
    159 	public function rewind()
    160 	{
    161 		if ($this -> Index > 0)
    162 		{
    163 		// If the worksheet was already iterated, XML file is reopened.
    164 		// Otherwise it should be at the beginning anyway
    165 		$this -> Content -> close();
    166 		$this -> Content -> open($this -> ContentPath);
    167 		$this -> Valid = true;
    168 
    169 		$this -> TableOpen = false;
    170 		$this -> RowOpen = false;
    171 
    172 		$this -> CurrentRow = null;
    173 		}
    174 
    175 		$this -> Index = 0;
    176 	}
    177 
    178 	/**
    179 	 * Return the current element.
    180 	 * Similar to the current() function for arrays in PHP
    181 	 *
    182 	 * @return mixed current element from the collection
    183 	 */
    184 	public function current()
    185 	{
    186 		if ($this -> Index == 0 && is_null($this -> CurrentRow))
    187 		{
    188 		$this -> next();
    189 		$this -> Index--;
    190 		}
    191 		return $this -> CurrentRow;
    192 	}
    193 
    194 	/** 
    195 	 * Move forward to next element. 
    196 	 * Similar to the next() function for arrays in PHP 
    197 	 */ 
    198 	public function next()
    199 	{
    200 		$this -> Index++;
    201 
    202 		$this -> CurrentRow = array();
    203 
    204 		if (!$this -> TableOpen)
    205 		{
    206 		$TableCounter = 0;
    207 		$SkipRead = false;
    208 
    209 		while ($this -> Valid = ($SkipRead || $this -> Content -> read()))
    210 		{
    211 			if ($SkipRead)
    212 			{
    213 			$SkipRead = false;
    214 			}
    215 
    216 			if ($this -> Content -> name == 'table:table' && $this -> Content -> nodeType != XMLReader::END_ELEMENT)
    217 			{
    218 			if ($TableCounter == $this -> CurrentSheet)
    219 			{
    220 				$this -> TableOpen = true;
    221 				break;
    222 			}
    223 
    224 			$TableCounter++;
    225 			$this -> Content -> next();
    226 			$SkipRead = true;
    227 			}
    228 		}
    229 		}
    230 
    231 		if ($this -> TableOpen && !$this -> RowOpen)
    232 		{
    233 		while ($this -> Valid = $this -> Content -> read())
    234 		{
    235 			switch ($this -> Content -> name)
    236 			{
    237 			case 'table:table':
    238 				$this -> TableOpen = false;
    239 				$this -> Content -> next('office:document-content');
    240 				$this -> Valid = false;
    241 				break 2;
    242 			case 'table:table-row':
    243 				if ($this -> Content -> nodeType != XMLReader::END_ELEMENT)
    244 				{
    245 				$this -> RowOpen = true;
    246 				break 2;
    247 				}
    248 				break;
    249 			}
    250 		}
    251 		}
    252 
    253 		if ($this -> RowOpen)
    254 		{
    255 		$LastCellContent = '';
    256 
    257 		while ($this -> Valid = $this -> Content -> read())
    258 		{
    259 			switch ($this -> Content -> name)
    260 			{
    261 			case 'table:table-cell':
    262 				if ($this -> Content -> nodeType == XMLReader::END_ELEMENT || $this -> Content -> isEmptyElement)
    263 				{
    264 				if ($this -> Content -> nodeType == XMLReader::END_ELEMENT)
    265 				{
    266 					$CellValue = $LastCellContent;
    267 				}
    268 				elseif ($this -> Content -> isEmptyElement)
    269 				{
    270 					$LastCellContent = '';
    271 					$CellValue = $LastCellContent;
    272 				}
    273 
    274 				$this -> CurrentRow[] = $LastCellContent;
    275 
    276 				if ($this -> Content -> getAttribute('table:number-columns-repeated') !== null)
    277 				{                                                                                            
    278 					$RepeatedColumnCount = $this -> Content -> getAttribute('table:number-columns-repeated');
    279 					// Checking if larger than one because the value is already added to the row once before
    280 					if ($RepeatedColumnCount > 1)
    281 					{
    282 					$this -> CurrentRow = array_pad($this -> CurrentRow, count($this -> CurrentRow) + $RepeatedColumnCount - 1, $LastCellContent);
    283 					}
    284 				}
    285 				}
    286 				else
    287 				{
    288 				$LastCellContent = '';
    289 				}
    290 			case 'text:p':
    291 				if ($this -> Content -> nodeType != XMLReader::END_ELEMENT)
    292 				{
    293 				$LastCellContent = $this -> Content -> readString();
    294 				}
    295 				break;
    296 			case 'table:table-row':
    297 				$this -> RowOpen = false;
    298 				break 2;
    299 			}
    300 		}
    301 		}
    302 
    303 		return $this -> CurrentRow;
    304 	}
    305 
    306 	/** 
    307 	 * Return the identifying key of the current element.
    308 	 * Similar to the key() function for arrays in PHP
    309 	 *
    310 	 * @return mixed either an integer or a string
    311 	 */ 
    312 	public function key()
    313 	{
    314 		return $this -> Index;
    315 	}
    316 
    317 	/** 
    318 	 * Check if there is a current element after calls to rewind() or next().
    319 	 * Used to check if we've iterated to the end of the collection
    320 	 *
    321 	 * @return boolean FALSE if there's nothing more to iterate over
    322 	 */ 
    323 	public function valid()
    324 	{
    325 		return $this -> Valid;
    326 	}
    327 
    328 	// !Countable interface method
    329 	/**
    330 	 * Ostensibly should return the count of the contained items but this just returns the number
    331 	 * of rows read so far. It's not really correct but at least coherent.
    332 	 */
    333 	public function count()
    334 	{
    335 		return $this -> Index + 1;
    336 	}
    337 }
    338 
    339 ?>