woocommerce/apigen/libs/Texy/texy/modules/TexyTableModule.php

310 lines
6.8 KiB
PHP

<?php
/**
* Texy! - human-readable text to HTML converter.
*
* @copyright Copyright (c) 2004, 2010 David Grudl
* @license GNU GENERAL PUBLIC LICENSE version 2 or 3
* @link http://texy.info
* @package Texy
*/
/**
* Table module.
*
* @copyright Copyright (c) 2004, 2010 David Grudl
* @package Texy
*/
final class TexyTableModule extends TexyModule
{
/** @var string CSS class for odd rows */
public $oddClass;
/** @var string CSS class for even rows */
public $evenClass;
private $disableTables;
public function __construct($texy)
{
$this->texy = $texy;
$texy->registerBlockPattern(
array($this, 'patternTable'),
'#^(?:'.TEXY_MODIFIER_HV.'\n)?' // .{color: red}
. '\|.*()$#mU', // | ....
'table'
);
}
/**
* Callback for:.
*
* .(title)[class]{style}>
* |------------------
* | xxx | xxx | xxx | .(..){..}[..]
* |------------------
* | aa | bb | cc |
*
* @param TexyBlockParser
* @param array regexp matches
* @param string pattern name
* @return TexyHtml|string|FALSE
*/
public function patternTable($parser, $matches)
{
if ($this->disableTables) return FALSE;
list(, $mMod) = $matches;
// [1] => .(title)[class]{style}<>_
$tx = $this->texy;
$el = TexyHtml::el('table');
$mod = new TexyModifier($mMod);
$mod->decorate($tx, $el);
$parser->moveBackward();
if ($parser->next('#^\|(\#|\=){2,}(?![|\#=+])(.+)\\1*\|? *'.TEXY_MODIFIER_H.'?()$#Um', $matches)) {
list(, , $mContent, $mMod) = $matches;
// [1] => # / =
// [2] => ....
// [3] => .(title)[class]{style}<>
$caption = $el->create('caption');
$mod = new TexyModifier($mMod);
$mod->decorate($tx, $caption);
$caption->parseLine($tx, $mContent);
}
$isHead = FALSE;
$colModifier = array();
$prevRow = array(); // rowSpan building helper
$rowCounter = 0;
$colCounter = 0;
$elPart = NULL;
$lineMode = FALSE; // rows must be separated by lines
while (TRUE) {
if ($parser->next('#^\|([=-])[+|=-]{2,}$#Um', $matches)) { // line
if ($lineMode) {
if ($matches[1] === '=') $isHead = !$isHead;
} else {
$isHead = !$isHead;
$lineMode = $matches[1] === '=';
}
$prevRow = array();
continue;
}
if ($parser->next('#^\|(.*)(?:|\|\ *'.TEXY_MODIFIER_HV.'?)()$#U', $matches)) {
// smarter head detection
if ($rowCounter === 0 && !$isHead && $parser->next('#^\|[=-][+|=-]{2,}$#Um', $foo)) {
$isHead = TRUE;
$parser->moveBackward();
}
if ($elPart === NULL) {
$elPart = $el->create($isHead ? 'thead' : 'tbody');
} elseif (!$isHead && $elPart->getName() === 'thead') {
$this->finishPart($elPart);
$elPart = $el->create('tbody');
}
// PARSE ROW
list(, $mContent, $mMod) = $matches;
// [1] => ....
// [2] => .(title)[class]{style}<>_
$elRow = TexyHtml::el('tr');
$mod = new TexyModifier($mMod);
$mod->decorate($tx, $elRow);
$rowClass = $rowCounter % 2 === 0 ? $this->oddClass : $this->evenClass;
if ($rowClass && !isset($mod->classes[$this->oddClass]) && !isset($mod->classes[$this->evenClass])) {
$elRow->attrs['class'][] = $rowClass;
}
$col = 0;
$elCell = NULL;
// special escape sequence \|
$mContent = str_replace('\\|', "\x13", $mContent);
$mContent = preg_replace('#(\[[^\]]*)\|#', "$1\x13", $mContent); // HACK: support for [..|..]
foreach (explode('|', $mContent) as $cell) {
$cell = strtr($cell, "\x13", '|');
// rowSpan
if (isset($prevRow[$col]) && ($lineMode || preg_match('#\^\ *$|\*??(.*)\ +\^$#AU', $cell, $matches))) {
$prevRow[$col]->rowSpan++;
if (!$lineMode) {
$cell = isset($matches[1]) ? $matches[1] : '';
}
$prevRow[$col]->text .= "\n" . $cell;
$col += $prevRow[$col]->colSpan;
$elCell = NULL;
continue;
}
// colSpan
if ($cell === '' && $elCell) {
$elCell->colSpan++;
unset($prevRow[$col]);
$col++;
continue;
}
// common cell
if (!preg_match('#(\*??)\ *'.TEXY_MODIFIER_HV.'??(.*)'.TEXY_MODIFIER_HV.'?\ *()$#AU', $cell, $matches)) continue;
list(, $mHead, $mModCol, $mContent, $mMod) = $matches;
// [1] => * ^
// [2] => .(title)[class]{style}<>_
// [3] => ....
// [4] => .(title)[class]{style}<>_
if ($mModCol) {
$colModifier[$col] = new TexyModifier($mModCol);
}
if (isset($colModifier[$col]))
$mod = clone $colModifier[$col];
else
$mod = new TexyModifier;
$mod->setProperties($mMod);
$elCell = new TexyTableCellElement;
$elCell->setName($isHead || ($mHead === '*') ? 'th' : 'td');
$mod->decorate($tx, $elCell);
$elCell->text = $mContent;
$elRow->add($elCell);
$prevRow[$col] = $elCell;
$col++;
}
// even up with empty cells
while ($col < $colCounter) {
if (isset($prevRow[$col]) && $lineMode) {
$prevRow[$col]->rowSpan++;
$prevRow[$col]->text .= "\n";
} else {
$elCell = new TexyTableCellElement;
$elCell->setName($isHead ? 'th' : 'td');
if (isset($colModifier[$col])) {
$colModifier[$col]->decorate($tx, $elCell);
}
$elRow->add($elCell);
$prevRow[$col] = $elCell;
}
$col++;
}
$colCounter = $col;
if ($elRow->count()) {
$elPart->add($elRow);
$rowCounter++;
} else {
// redundant row
foreach ($prevRow as $elCell) $elCell->rowSpan--;
}
continue;
}
break;
}
if ($elPart === NULL) {
// invalid table
return FALSE;
}
if ($elPart->getName() === 'thead') {
// thead is optional, tbody is required
$elPart->setName('tbody');
}
$this->finishPart($elPart);
// event listener
$tx->invokeHandlers('afterTable', array($parser, $el, $mod));
return $el;
}
/**
* Parse text in all cells.
* @param TexyHtml
* @return void
*/
private function finishPart($elPart)
{
$tx = $this->texy;
foreach ($elPart->getChildren() as $elRow)
{
foreach ($elRow->getChildren() as $elCell)
{
if ($elCell->colSpan > 1) {
$elCell->attrs['colspan'] = $elCell->colSpan;
}
if ($elCell->rowSpan > 1) {
$elCell->attrs['rowspan'] = $elCell->rowSpan;
}
$text = rtrim($elCell->text);
if (strpos($text, "\n") !== FALSE) {
// multiline parse as block
// HACK: disable tables
$this->disableTables = TRUE;
$elCell->parseBlock($tx, Texy::outdent($text));
$this->disableTables = FALSE;
} else {
$elCell->parseLine($tx, ltrim($text));
}
if ($elCell->getText() === '') {
$elCell->setText("\xC2\xA0"); // &nbsp;
}
}
}
}
}
/**
* Table cell TD / TH.
* @package Texy
*/
class TexyTableCellElement extends TexyHtml
{
/** @var int */
public $colSpan = 1;
/** @var int */
public $rowSpan = 1;
/** @var string */
public $text;
}