maio
[h2N7SspZmY.git] / lib / plugins / columns / action.php
blob93048ea0f104a791f7123d05eb866b996cecabd0
1 <?php
3 /**
4 * Plugin Columns: Layout parser
6 * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
7 * @author Mykola Ostrovskyy <spambox03@mail.ru>
8 */
10 /* Must be run within Dokuwiki */
11 if(!defined('DOKU_INC')) die();
13 if(!defined('DOKU_PLUGIN')) define('DOKU_PLUGIN', DOKU_INC . 'lib/plugins/');
14 require_once(DOKU_PLUGIN . 'action.php');
15 require_once(DOKU_PLUGIN . 'columns/info.php');
16 require_once(DOKU_PLUGIN . 'columns/rewriter.php');
18 class action_plugin_columns extends DokuWiki_Action_Plugin {
20 var $block;
21 var $currentBlock;
22 var $currentSectionLevel;
23 var $sectionEdit;
25 /**
26 * Return some info
28 function getInfo() {
29 return columns_getInfo('layout parser');
32 /**
33 * Register callbacks
35 function register(&$controller) {
36 $controller->register_hook('PARSER_HANDLER_DONE', 'AFTER', $this, 'handle');
39 /**
42 function handle(&$event, $param) {
43 $this->_reset();
44 $this->_buildLayout($event);
45 $rewriter = new instruction_rewriter();
46 foreach ($this->block as $block) {
47 $block->processAttributes($event);
48 $rewriter->addCorrections($block->getCorrections());
50 $rewriter->process($event->data->calls);
53 /**
54 * Find all columns instructions and construct columns layout based on them
56 function _buildLayout(&$event) {
57 $calls = count($event->data->calls);
58 for ($c = 0; $c < $calls; $c++) {
59 $call =& $event->data->calls[$c];
60 switch ($call[0]) {
61 case 'section_open':
62 $this->currentSectionLevel = $call[1][0];
63 $this->currentBlock->openSection();
64 break;
66 case 'section_close':
67 $this->currentBlock->closeSection($c);
68 break;
70 case 'plugin':
71 if ($call[1][0] == 'columns') {
72 $this->_handleColumns($c, $call[1][1][0], $this->_detectSectionEdit($event->data->calls, $c));
74 break;
79 /**
80 * Reset internal state
82 function _reset() {
83 $this->block = array();
84 $this->block[0] = new columns_root_block();
85 $this->currentBlock = $this->block[0];
86 $this->currentSectionLevel = 0;
87 $this->sectionEdit = array();
90 /**
93 function _detectSectionEdit($call, $start) {
94 $result = null;
95 $calls = count($call);
96 for ($c = $start + 1; $c < $calls; $c++) {
97 switch ($call[$c][0]) {
98 case 'section_close':
99 case 'p_open':
100 case 'p_close':
101 /* Skip these instructions */
102 break;
104 case 'section_edit':
105 if (end($this->sectionEdit) != $c) {
106 $this->sectionEdit[] = $c;
107 $result = array('call' => $c, 'data' => $call[$c][1]);
109 break 2;
111 case 'plugin':
112 break ($call[$c][1][0] == 'columns') ? 1 : 2;
114 default:
115 break 2;
118 return $result;
124 function _handleColumns($callIndex, $state, $sectionEdit) {
125 switch ($state) {
126 case DOKU_LEXER_ENTER:
127 $this->currentBlock = new columns_block(count($this->block), $this->currentBlock);
128 $this->currentBlock->addColumn($callIndex, $this->currentSectionLevel);
129 $this->currentBlock->startSection($sectionEdit);
130 $this->block[] = $this->currentBlock;
131 break;
133 case DOKU_LEXER_MATCHED:
134 $this->currentBlock->addColumn($callIndex, $this->currentSectionLevel);
135 $this->currentBlock->startSection($sectionEdit);
136 break;
138 case DOKU_LEXER_EXIT:
139 $this->currentBlock->endSection($sectionEdit);
140 $this->currentBlock->close($callIndex);
141 $this->currentBlock = $this->currentBlock->getParent();
142 break;
147 class columns_root_block {
149 var $sectionLevel;
150 var $call;
153 * Constructor
155 function columns_root_block() {
156 $this->sectionLevel = 0;
157 $this->call = array();
163 function getParent() {
164 return $this;
168 * Collect stray <newcolumn> tags
170 function addColumn($callIndex, $sectionLevel) {
171 $this->call[] = $callIndex;
177 function openSection() {
178 $this->sectionLevel++;
184 function closeSection($callIndex) {
185 if ($this->sectionLevel > 0) {
186 $this->sectionLevel--;
188 else {
189 $this->call[] = $callIndex;
196 function startSection($callInfo) {
202 function endSection($callInfo) {
206 * Collect stray </colums> tags
208 function close($callIndex) {
209 $this->call[] = $callIndex;
215 function processAttributes(&$event) {
219 * Delete all captured tags
221 function getCorrections() {
222 $correction = array();
223 foreach ($this->call as $call) {
224 $correction[] = new instruction_rewriter_delete($call);
226 return $correction;
230 class columns_block {
232 var $id;
233 var $parent;
234 var $column;
235 var $currentColumn;
236 var $closed;
239 * Constructor
241 function columns_block($id, $parent) {
242 $this->id = $id;
243 $this->parent = $parent;
244 $this->column = array();
245 $this->currentColumn = null;
246 $this->closed = false;
252 function getParent() {
253 return $this->parent;
259 function addColumn($callIndex, $sectionLevel) {
260 if ($this->currentColumn != null) {
261 $this->currentColumn->close($callIndex);
263 $this->currentColumn = new columns_column($callIndex, $sectionLevel);
264 $this->column[] = $this->currentColumn;
270 function openSection() {
271 $this->currentColumn->openSection();
277 function closeSection($callIndex) {
278 $this->currentColumn->closeSection($callIndex);
284 function startSection($callInfo) {
285 $this->currentColumn->startSection($callInfo);
291 function endSection($callInfo) {
292 $this->currentColumn->endSection($callInfo);
298 function close($callIndex) {
299 $this->currentColumn->close($callIndex);
300 $this->closed = true;
304 * Convert raw attributes and layout information into column attributes
306 function processAttributes(&$event) {
307 $columns = count($this->column);
308 for ($c = 0; $c < $columns; $c++) {
309 $call =& $event->data->calls[$this->column[$c]->getOpenCall()];
310 if ($c == 0) {
311 $this->_loadBlockAttributes($call[1][1][1]);
312 $this->column[0]->addAttribute('columns', $columns);
313 $this->column[0]->addAttribute('class', 'first');
315 else {
316 $this->_loadColumnAttributes($c, $call[1][1][1]);
317 if ($c == ($columns - 1)) {
318 $this->column[$c]->addAttribute('class', 'last');
321 $this->column[$c]->addAttribute('block-id', $this->id);
322 $this->column[$c]->addAttribute('column-id', $c + 1);
323 $call[1][1][1] = $this->column[$c]->getAttributes();
328 * Convert raw attributes into column attributes
330 function _loadBlockAttributes($attribute) {
331 $column = -1;
332 $nextColumn = -1;
333 foreach ($attribute as $a) {
334 list($name, $temp) = $this->_parseAttribute($a);
335 if ($name == 'width') {
336 if (($column == -1) && array_key_exists('column-width', $temp)) {
337 $this->column[0]->addAttribute('table-width', $temp['column-width']);
339 $nextColumn = $column + 1;
341 if (($column >= 0) && ($column < count($this->column))) {
342 $this->column[$column]->addAttributes($temp);
344 $column = $nextColumn;
349 * Convert raw attributes into column attributes
351 function _loadColumnAttributes($column, $attribute) {
352 foreach ($attribute as $a) {
353 list($name, $temp) = $this->_parseAttribute($a);
354 $this->column[$column]->addAttributes($temp);
361 function _parseAttribute($attribute) {
362 static $syntax = array(
363 '/^left|right|center|justify$/' => 'text-align',
364 '/^top|middle|bottom$/' => 'vertical-align',
365 '/^[lrcjtmb]{1,2}$/' => 'align',
366 '/^continue|\.{3}$/' => 'continue',
367 '/^(\*?)((?:-|(?:\d+\.?|\d*\.\d+)(?:%|em|px|cm|mm|in|pt)))(\*?)$/' => 'width'
369 $result = array();
370 $attributeName = '';
371 foreach ($syntax as $pattern => $name) {
372 if (preg_match($pattern, $attribute, $match) == 1) {
373 $attributeName = $name;
374 break;
377 switch ($attributeName) {
378 case 'text-align':
379 case 'vertical-align':
380 $result[$attributeName] = $match[0];
381 break;
383 case 'align':
384 $result = $this->_parseAlignAttribute($match[0]);
385 break;
387 case 'continue':
388 $result[$attributeName] = 'on';
389 break;
391 case 'width':
392 $result = $this->_parseWidthAttribute($match);
393 break;
395 return array($attributeName, $result);
401 function _parseAlignAttribute($syntax) {
402 $result = array();
403 $align1 = $this->_getAlignStyle($syntax{0});
404 if (strlen($syntax) == 2) {
405 $align2 = $this->_getAlignStyle($syntax{1});
406 if ($align1 != $align2) {
407 $result[$align1] = $this->_getAlignment($syntax{0});
408 $result[$align2] = $this->_getAlignment($syntax{1});
411 else{
412 $result[$align1] = $this->_getAlignment($syntax{0});
414 return $result;
420 function _getAlignStyle($align) {
421 return preg_match('/[lrcj]/', $align) ? 'text-align' : 'vertical-align';
427 function _parseWidthAttribute($syntax) {
428 $result = array();
429 if ($syntax[2] != '-') {
430 $result['column-width'] = $syntax[2];
432 $align = $syntax[1] . '-' . $syntax[3];
433 if ($align != '-') {
434 $result['text-align'] = $this->_getAlignment($align);
436 return $result;
440 * Returns column text alignment
442 function _getAlignment($syntax) {
443 static $align = array(
444 'l' => 'left', '-*' => 'left',
445 'r' => 'right', '*-' => 'right',
446 'c' => 'center', '*-*' => 'center',
447 'j' => 'justify',
448 't' => 'top',
449 'm' => 'middle',
450 'b' => 'bottom'
452 if (array_key_exists($syntax, $align)) {
453 return $align[$syntax];
455 else {
456 return '';
461 * Returns a list of corrections that have to be applied to the instruction array
463 function getCorrections() {
464 if ($this->closed) {
465 $correction = $this->_fixSections();
467 else {
468 $correction = $this->_deleteColumns();
470 return $correction;
474 * Re-write section open/close instructions to produce valid HTML
475 * See columns:design#section_fixing for details
477 function _fixSections() {
478 $correction = array();
479 foreach ($this->column as $column) {
480 $correction = array_merge($correction, $column->getCorrections());
482 return $correction;
488 function _deleteColumns() {
489 $correction = array();
490 foreach ($this->column as $column) {
491 $correction[] = $column->delete();
493 return $correction;
497 class columns_attributes_bag {
499 var $attribute;
502 * Constructor
504 function columns_attributes_bag() {
505 $this->attribute = array();
511 function addAttribute($name, $value) {
512 $this->attribute[$name] = $value;
518 function addAttributes($attribute) {
519 if (is_array($attribute) && (count($attribute) > 0)) {
520 $this->attribute = array_merge($this->attribute, $attribute);
527 function getAttribute($name) {
528 $result = '';
529 if (array_key_exists($name, $this->attribute)) {
530 $result = $this->attribute[$name];
532 return $result;
538 function getAttributes() {
539 return $this->attribute;
543 class columns_column extends columns_attributes_bag {
545 var $open;
546 var $close;
547 var $sectionLevel;
548 var $sectionOpen;
549 var $sectionClose;
550 var $sectionStart;
551 var $sectionEnd;
554 * Constructor
556 function columns_column($open, $sectionLevel) {
557 parent::columns_attributes_bag();
559 $this->open = $open;
560 $this->close = -1;
561 $this->sectionLevel = $sectionLevel;
562 $this->sectionOpen = false;
563 $this->sectionClose = -1;
564 $this->sectionStart = null;
565 $this->sectionEnd = null;
571 function getOpenCall() {
572 return $this->open;
578 function openSection() {
579 $this->sectionOpen = true;
585 function closeSection($callIndex) {
586 if ($this->sectionClose == -1) {
587 $this->sectionClose = $callIndex;
594 function startSection($callInfo) {
595 $this->sectionStart = $callInfo;
601 function endSection($callInfo) {
602 $this->sectionEnd = $callInfo;
608 function close($callIndex) {
609 $this->close = $callIndex;
615 function delete() {
616 return new instruction_rewriter_delete($this->open);
620 * Re-write section open/close instructions to produce valid HTML
621 * See columns:design#section_fixing for details
623 function getCorrections() {
624 $result = array();
625 $deleteSectionClose = ($this->sectionClose != -1);
626 $closeSection = $this->sectionOpen;
627 if ($this->sectionStart != null) {
628 $result = array_merge($result, $this->_moveStartSectionEdit());
630 if (($this->getAttribute('continue') == 'on') && ($this->sectionLevel > 0)) {
631 $result[] = $this->_openStartSection();
632 /* Ensure that this section will be properly closed */
633 $deleteSectionClose = false;
634 $closeSection = true;
636 if ($deleteSectionClose) {
637 /* Remove first section_close from the column to prevent </div> in the middle of the column */
638 $result[] = new instruction_rewriter_delete($this->sectionClose);
640 if (($closeSection) || ($this->sectionEnd != null)) {
641 $result = array_merge($result, $this->_closeLastSection($closeSection));
643 return $result;
647 * Moves section_edit at the start of the column out of the column
649 function _moveStartSectionEdit() {
650 $result = array();
651 $result[0] = new instruction_rewriter_insert($this->open);
652 $result[0]->addCall('section_edit', $this->sectionStart['data']);
653 $result[1] = new instruction_rewriter_delete($this->sectionStart['call']);
654 return $result;
658 * Insert section_open at the start of the column
660 function _openStartSection() {
661 $insert = new instruction_rewriter_insert($this->open + 1);
662 $insert->addCall('section_open', array($this->sectionLevel));
663 return $insert;
667 * Close last open section in the column
669 function _closeLastSection($closeSection) {
670 $result = array();
671 $result[0] = new instruction_rewriter_insert($this->close);
672 if ($closeSection) {
673 $result[0]->addCall('section_close', array());
675 if ($this->sectionEnd != null) {
676 $result[0]->addCall('section_edit', $this->sectionEnd['data']);
677 $result[1] = new instruction_rewriter_delete($this->sectionEnd['call']);
679 return $result;