1<?php 2 3require_once DOKU_PLUGIN.'odt/ODT/elements/ODTStateElement.php'; 4require_once DOKU_PLUGIN.'odt/ODT/elements/ODTContainerElement.php'; 5 6/** 7 * ODTElementTable: 8 * Class for handling the table element. 9 * 10 * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) 11 * @author LarsDW223 12 */ 13class ODTElementTable extends ODTStateElement implements iContainerAccess 14{ 15 // Table specific state data 16 protected $container = NULL; 17 protected $containerPos = NULL; 18 protected $table_column_styles = array (); 19 protected $table_style = NULL; 20 protected $table_autocols = false; 21 protected $table_maxcols = 0; 22 protected $table_curr_column = 0; 23 protected $table_row_count = 0; 24 protected $own_max_width = NULL; 25 26 // Flag indicating that a table was created inside of a list 27 protected $list_interrupted = false; 28 29 /** 30 * Constructor. 31 * ($numrows is currently unused) 32 */ 33 public function __construct($style_name=NULL, $maxcols = 0, $numrows = 0) { 34 parent::__construct(); 35 $this->setClass ('table'); 36 if (isset($style_name)) { 37 $this->setStyleName ($style_name); 38 } 39 $this->setTableMaxColumns($maxcols); 40 if ($maxcols == 0) { 41 $this->setTableAutoColumns(true); 42 } 43 $this->container = new ODTContainerElement($this); 44 } 45 46 /** 47 * Return the elements name. 48 * 49 * @return string The ODT XML element name. 50 */ 51 public function getElementName () { 52 return ('table:table'); 53 } 54 55 /** 56 * Return string with encoded opening tag. 57 * 58 * @return string The ODT XML code to open this element. 59 */ 60 public function getOpeningTag () { 61 $style_name = $this->getStyleName(); 62 if (!isset($style_name)) { 63 $encoded = '<table:table>'; 64 } else { 65 $encoded .= '<table:table table:style-name="'.$style_name.'">'; 66 } 67 $maxcols = $this->getTableMaxColumns(); 68 $count = $this->getCount(); 69 if ($maxcols == 0) { 70 // Try to automatically detect the number of columns. 71 $this->setTableAutoColumns(true); 72 } else { 73 $this->setTableAutoColumns(false); 74 } 75 76 // Add column definitions placeholder. 77 // This will be replaced on tabelClose()/getClosingTag() 78 $encoded .= '<ColumnsPlaceholder'.$count.'>'; 79 80 // We start with the first column 81 $this->setTableCurrentColumn(0); 82 83 return $encoded; 84 } 85 86 /** 87 * Return string with encoded closing tag. 88 * 89 * @return string The ODT XML code to close this element. 90 */ 91 public function getClosingTag (&$content = NULL) { 92 // Generate table column definitions and replace the placeholder with it 93 $count = $this->getCount(); 94 $max = $this->getTableMaxColumns(); 95 if ($max > 0 && isset($content)) { 96 $column_defs = ''; 97 for ($index = 0 ; $index < $max ; $index++) { 98 $styleName = $this->getTableColumnStyleName($index); 99 if (!empty($styleName)) { 100 $column_defs .= '<table:table-column table:style-name="'.$styleName.'"/>'; 101 } else { 102 $column_defs .= '<table:table-column/>'; 103 } 104 } 105 $content = 106 str_replace ('<ColumnsPlaceholder'.$count.'>', $column_defs, $content); 107 $content = 108 str_replace ('<MaxColsPlaceholder'.$count.'>', $max, $content); 109 } 110 111 return '</table:table>'; 112 } 113 114 /** 115 * Are we in a paragraph or not? 116 * As a table we are not. 117 * 118 * @return boolean 119 */ 120 public function getInParagraph() { 121 return false; 122 } 123 124 /** 125 * Determine and set the parent for this element. 126 * As a table the previous element is our parent. 127 * 128 * If the table is nested in another table, then the surrounding 129 * table is the parent! 130 * 131 * @param ODTStateElement $previous 132 */ 133 public function determineParent(ODTStateElement $previous) { 134 $this->container->determineParent($previous); 135 if ($this->isNested ()) { 136 $this->containerPos = array(); 137 $this->getParent()->determinePositionInContainer($this->containerPos, $previous); 138 } 139 } 140 141 /** 142 * Set table column styles 143 * 144 * @param array $value 145 */ 146 public function setTableColumnStyles($value) { 147 $this->table_column_styles = $value; 148 } 149 150 /** 151 * Set table column style for $column 152 * 153 * @param array $value 154 */ 155 public function setTableColumnStyleName($column, $style_name) { 156 $this->table_column_styles [$column] = $style_name; 157 } 158 159 /** 160 * Get table column styles 161 * 162 * @return array 163 */ 164 public function getTableColumnStyles() { 165 return $this->table_column_styles; 166 } 167 168 /** 169 * Set table column style for $column 170 * 171 * @param array $value 172 */ 173 public function getTableColumnStyleName($column) { 174 return $this->table_column_styles [$column]; 175 } 176 177 /** 178 * Set flag if table columns shall be generated automatically. 179 * (automatically detect the number of columns) 180 * 181 * @param boolean $value 182 */ 183 public function setTableAutoColumns($value) { 184 $this->table_autocols = $value; 185 } 186 187 /** 188 * Get flag if table columns shall be generated automatically. 189 * (automatically detect the number of columns) 190 * 191 * @return boolean 192 */ 193 public function getTableAutoColumns() { 194 return $this->table_autocols; 195 } 196 197 /** 198 * Set maximal number of columns. 199 * 200 * @param integer $value 201 */ 202 public function setTableMaxColumns($value) { 203 $this->table_maxcols = $value; 204 } 205 206 /** 207 * Get maximal number of columns. 208 * 209 * @return integer 210 */ 211 public function getTableMaxColumns() { 212 return $this->table_maxcols; 213 } 214 215 /** 216 * Set current column. 217 * 218 * @param integer $value 219 */ 220 public function setTableCurrentColumn($value) { 221 $this->table_curr_column = $value; 222 } 223 224 /** 225 * Get current column. 226 * 227 * @return integer 228 */ 229 public function getTableCurrentColumn() { 230 return $this->table_curr_column; 231 } 232 233 /** 234 * Get the predefined style name for the current 235 * table column. 236 * 237 * @return string 238 */ 239 public function getCurrentTableColumnStyleName() { 240 $table_column_styles = $this->getTableColumnStyles(); 241 $curr_column = $this->getTableCurrentColumn(); 242 return $table_column_styles [$curr_column]; 243 } 244 245 /** 246 * Set flag if current list is interrupted (by a table) or not. 247 * 248 * @param boolean $value 249 */ 250 public function setListInterrupted($value) { 251 $this->list_interrupted = $value; 252 } 253 254 /** 255 * Get flag if current list is interrupted (by a table) or not. 256 * 257 * @return boolean 258 */ 259 public function getListInterrupted() { 260 return $this->list_interrupted; 261 } 262 263 /** 264 * Increae the number of rows 265 * 266 * @param boolean $value 267 */ 268 public function increaseRowCount() { 269 $this->table_row_count++; 270 } 271 272 public function getRowCount() { 273 return $this->table_row_count; 274 } 275 276 /** 277 * Is this table a nested table (inserted into another table)? 278 * 279 * @return boolean 280 */ 281 public function isNested () { 282 return $this->container->isNested(); 283 } 284 285 public function addNestedContainer (iContainerAccess $nested) { 286 $this->container->addNestedContainer ($nested); 287 } 288 289 public function getNestedContainers () { 290 return $this->container->getNestedContainers (); 291 } 292 293 public function determinePositionInContainer (array &$data, ODTStateElement $current) { 294 $data ['column'] = $this->getTableCurrentColumn(); 295 $cell = NULL; 296 while (isset($current)) { 297 if ($current->getClass() == 'table-cell') { 298 $cell = $current; 299 break; 300 } 301 if ($current->getClass() == 'table') { 302 break; 303 } 304 $current = $current->getParent(); 305 } 306 if (isset($cell)) { 307 $data ['cell'] = $cell; 308 } 309 } 310 311 public function getMaxWidthOfNestedContainer (ODTInternalParams $params, array $data) { 312 if (!isset($this->own_max_width)) { 313 // We do not know our own width yet. Calculate it first. 314 $this->own_max_width = $this->getMaxWidth($params); 315 } 316 317 $column = $data ['column']; 318 $cell = $data ['cell']; 319 320 $cell_style = $cell->getStyle(); 321 $padding = 0; 322 if ($cell_style->getProperty('padding-left') != NULL 323 || 324 $cell_style->getProperty('padding-right') != NULL) { 325 $value = $cell_style->getProperty('padding-left'); 326 $value = $params->document->toPoints($value, 'y'); 327 $padding += $value; 328 $value = $cell_style->getProperty('padding-right'); 329 $value = $params->document->toPoints($value, 'y'); 330 $padding += $value; 331 } else if ($cell_style->getProperty('padding') != NULL) { 332 $value = $cell_style->getProperty('padding'); 333 $value = $params->document->toPoints($value, 'y'); 334 $padding += 2 * $value; 335 } 336 337 $table_column_styles = $this->getTableColumnStyles(); 338 $style_name = $table_column_styles [$column-1]; 339 $style_obj = $params->document->getStyle($style_name); 340 if (isset($style_obj)) { 341 $width = $style_obj->getProperty('column-width'); 342 $width = trim ($width, 'pt'); 343 $width -= $padding; 344 } 345 346 // Compare with total table width 347 if (isset($this->own_max_width)) { 348 $table_width = $params->units->getDigits ($params->units->toPoints($this->own_max_width)); 349 350 if ($table_width < $width) { 351 $width = $table_width; 352 } 353 } 354 355 return $width.'pt'; 356 } 357 358 public function getMaxWidth (ODTInternalParams $params) { 359 $tableStyle = $this->getStyle(); 360 if (!$this->isNested ()) { 361 // Get max page width in points. 362 $maxPageWidth = $params->document->getAbsWidthMindMargins (); 363 $maxPageWidthPt = $params->units->getDigits ($params->units->toPoints($maxPageWidth.'cm')); 364 365 // Get table left margin 366 $leftMargin = $tableStyle->getProperty('margin-left'); 367 if (!isset($leftMargin)) { 368 $leftMarginPt = 0; 369 } else { 370 $leftMarginPt = $params->units->getDigits ($params->units->toPoints($leftMargin)); 371 } 372 373 // Get table right margin 374 $rightMargin = $tableStyle->getProperty('margin-right'); 375 if (!isset($rightMargin)) { 376 $rightMarginPt = 0; 377 } else { 378 $rightMarginPt = $params->units->getDigits ($params->units->toPoints($rightMargin)); 379 } 380 381 // Get table width 382 $width = $tableStyle->getProperty('width'); 383 if (isset($width)) { 384 $widthPt = $params->units->getDigits ($params->units->toPoints($width)); 385 } 386 387 if (!isset($width)) { 388 $width = $maxPageWidthPt - $leftMarginPt - $rightMarginPt; 389 } else { 390 $width = $widthPt; 391 } 392 $width = $width.'pt'; 393 } else { 394 // If this table is nested in another container we have to ask it's parent 395 // for the allowed max width 396 $width = $this->getParent()->getMaxWidthOfNestedContainer($params, $this->containerPos); 397 } 398 399 return $width; 400 } 401 402 /** 403 * This function replaces the width of $table with the 404 * value of all column width added together. If a column has 405 * no width set then the function will abort and change nothing. 406 * 407 * @param ODTDocument $doc The current document 408 * @param ODTElementTable $table The table to be adjusted 409 */ 410 public function adjustWidth (ODTInternalParams $params, $allowNested=false) { 411 if ($this->isNested () && !$allowNested) { 412 // Do not do anything if this is a nested table. 413 // Only if the function is called for the parent/root table 414 // then the width of the nested tables will be calculated. 415 return; 416 } 417 $matches = array (); 418 419 $table_style_name = $this->getStyleName(); 420 if (empty($table_style_name)) { 421 return; 422 } 423 424 $max_width = $this->getMaxWidth($params); 425 $width = $this->adjustWidthInternal ($params, $max_width); 426 427 $style_obj = $params->document->getStyle($table_style_name); 428 if (isset($style_obj)) { 429 $style_obj->setProperty('width', $width.'pt'); 430 if (!$this->isNested ()) { 431 // Calculate rel width in relation to maximum page width 432 $maxPageWidth = $params->document->getAbsWidthMindMargins (); 433 $maxPageWidth = $params->units->getDigits ($params->units->toPoints($maxPageWidth.'cm')); 434 if ($maxPageWidth != 0) { 435 $rel_width = round(($width * 100)/$maxPageWidth); 436 } 437 } else { 438 // Calculate rel width in relation to maximum table width 439 if ($max_width != 0) { 440 $rel_width = round(($width * 100)/$max_width); 441 } 442 } 443 $style_obj->setProperty('rel-width', $rel_width.'%'); 444 } 445 446 // Now adjust all nested containers too 447 $nested = $this->getNestedContainers (); 448 foreach ($nested as $container) { 449 $container->adjustWidth ($params, true); 450 } 451 } 452 453 public function adjustWidthInternal (ODTInternalParams $params, $maxWidth) { 454 $empty = array(); 455 $relative = array(); 456 $anyWidthFound = false; 457 $onlyAbsWidthFound = true; 458 459 $tableStyle = $this->getStyle(); 460 461 // First step: 462 // - convert all absolute widths to points 463 // - build the sum of all absolute column width values (if any) 464 // - build the sum of all relative column width values (if any) 465 $abs_sum = 0; 466 $table_column_styles = $this->getTableColumnStyles(); 467 $replace = true; 468 for ($index = 0 ; $index < $this->getTableMaxColumns() ; $index++ ) { 469 $style_name = $table_column_styles [$index]; 470 $style_obj = $params->document->getStyle($style_name); 471 if (isset($style_obj)) { 472 if ($style_obj->getProperty('rel-column-width') != NULL) { 473 $width = $style_obj->getProperty('rel-column-width'); 474 $length = strlen ($width); 475 $width = trim ($width, '*'); 476 477 // Add column style object to relative array 478 // We need convert it to an absolute width 479 $entry = array(); 480 $entry ['width'] = $width; 481 $entry ['obj'] = $style_obj; 482 $relative [] = $entry; 483 484 $abs_sum += (($width/10)/100) * $maxWidth; 485 $onlyAbsWidthFound = false; 486 $anyWidthFound = true; 487 } else if ($style_obj->getProperty('column-width') != NULL) { 488 $width = $style_obj->getProperty('column-width'); 489 $length = strlen ($width); 490 $width = $params->document->toPoints($width, 'x'); 491 $abs_sum += (float) trim ($width, 'pt'); 492 $anyWidthFound = true; 493 } else { 494 // Add column style object to empty array 495 // We need to assign a width to this column 496 $empty [] = $style_obj; 497 $onlyAbsWidthFound = false; 498 } 499 } 500 } 501 502 // Convert max width to points 503 $maxWidth = $params->units->toPoints($maxWidth); 504 $maxWidth = $params->units->getDigits($maxWidth); 505 506 // The remaining absolute width is the max width minus the sum of 507 // all absolute width values 508 $absWidthLeft = $maxWidth - $abs_sum; 509 510 // Calculate the relative width left 511 // (e.g. if the absolute width is the half of the max width 512 // then the relative width left if 50%) 513 if ($maxWidth != 0) { 514 $relWidthLeft = 100-(($absWidthLeft/$maxWidth)*100); 515 } 516 517 // Give all empty columns a width 518 $maxEmpty = count($empty); 519 foreach ($empty as $column) { 520 //$width = ($relWidthLeft/$maxEmpty) * $absWidthLeft; 521 $width = $absWidthLeft/$maxEmpty; 522 $column->setProperty('column-width', $width.'pt'); 523 $column->setProperty('rel-column-width', NULL); 524 } 525 526 // Convert all relative width to absolute 527 foreach ($relative as $column) { 528 $width = (($column ['width']/10)/100) * $maxWidth; 529 $column ['obj']->setProperty('column-width', $width.'pt'); 530 $column ['obj']->setProperty('rel-column-width', NULL); 531 } 532 533 // If all columns have a fixed absolute width set then that means 534 // the table shall have the width of all comuns added together 535 // and not the maximum available width. Return $abs_sum. 536 if ($onlyAbsWidthFound && $anyWidthFound) { 537 return $abs_sum; 538 } 539 return $maxWidth; 540 } 541} 542