1<?php 2/** 3 * DokuWiki plugin Diagram, Main component. 4 * 5 * Constructs diagrams. 6 * See a full description at http://nikita.melnichenko.name/projects/dokuwiki-diagram/. 7 * 8 * Should work with any DokuWiki version >= 20070626. 9 * Tested with DokuWiki versions 20090214, 20091225, 20110525a, 20121013, 20130510a, 10 * 20131208, 20140505e, 20140929d, 20150810a, 20160626e, 20170219e, 20180422a, 20200729. 11 * 12 * Install to lib/plugins/diagram/syntax/main.php. 13 * 14 * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) 15 * @author Nikita Melnichenko [http://nikita.melnichenko.name] 16 * @copyright Copyright 2007-2021, Nikita Melnichenko 17 * 18 * Thanks for help to: 19 * - Anika Henke <anika[at]selfthinker.org> 20 */ 21 22// includes 23if(!defined('DOKU_INC')) define('DOKU_INC',realpath(dirname(__FILE__).'/../../').'/'); 24if(!defined('DOKU_PLUGIN')) define('DOKU_PLUGIN',DOKU_INC.'lib/plugins/'); 25require_once(DOKU_PLUGIN.'syntax.php'); 26 27/** 28 * DokuWiki plugin Diagram, Main component. 29 * 30 * Constructs diagrams. 31 */ 32class syntax_plugin_diagram_main extends DokuWiki_Syntax_Plugin 33{ 34 /** 35 * Tag name in wiki text. 36 * 37 * @staticvar string 38 */ 39 var $tag_name = 'diagram'; 40 41 /** 42 * Splitter tag name in wiki text. 43 * 44 * Must be = syntax_plugin_diagram_splitter::$tag_name. 45 * Copied for compability with PHP4. 46 * 47 * @staticvar string 48 */ 49 var $tag_name_splitter = '_diagram_'; 50 51 /** 52 * CSS classes used in the diagram. 53 * 54 * @staticvar array 55 */ 56 var $css_classes = array( 57 /* spacers */ 58 'spacer-horizontal' => 'd-sh', 59 'spacer-vertical' => 'd-sv', 60 /* block */ 61 'block' => 'd-b', 62 /* connection borders */ 63 'border-right-solid' => 'd-brs', 64 'border-right-dashed' => 'd-brd', 65 'border-bottom-solid' => 'd-bbs', 66 'border-bottom-dashed' => 'd-bbd', 67 /* arrow directions */ 68 'arrow-top' => 'd-at', 69 'arrow-right' => 'd-ar', 70 'arrow-bottom' => 'd-ab', 71 'arrow-left' => 'd-al', 72 'arrow-inside' => 'd-ai' 73 ); 74 75 /** 76 * Get syntax type. 77 * 78 * @return string one of the mode types defined in $PARSER_MODES in parser.php 79 */ 80 function getType () 81 { 82 // containers are complex modes that can contain many other modes 83 // plugin generates table, so type should be container 84 return 'container'; 85 } 86 87 /** 88 * Get paragraph type. 89 * 90 * Defines how this syntax is handled regarding paragraphs. This is important 91 * for correct XHTML nesting. Should return one of the following: 92 * - 'normal' - The plugin can be used inside paragraphs 93 * - 'block' - Open paragraphs need to be closed before plugin output 94 * - 'stack' - Special case. Plugin wraps other paragraphs. 95 * 96 * @return string 97 */ 98 function getPType () 99 { 100 // table cannot be put inside paragraphs 101 return 'block'; 102 } 103 104 /** 105 * Get position of plugin's mode in decision list. 106 * 107 * @return integer 108 */ 109 function getSort () 110 { 111 // position doesn't matter 112 return 999; 113 } 114 115 /** 116 * Connect pattern to lexer. 117 * 118 * @param string $mode 119 */ 120 function connectTo ($mode) 121 { 122 // parse all content in one shot 123 $this->Lexer->addSpecialPattern('<'.$this->tag_name.'>.*?</'.$this->tag_name.'>', 124 $mode, 'plugin_diagram_main'); 125 } 126 127 /** 128 * Handle the match. 129 * 130 * @param string $match 131 * @param integer $state one of lexer states defined in lexer.php 132 * @param integer $pos position of first 133 * @param Doku_Handler $handler 134 * @return array data for rendering 135 */ 136 function handle ($match, $state, $pos, Doku_Handler $handler) 137 { 138 // strip tags 139 $tag_name_len = strlen($this->tag_name); 140 $content = substr($match, $tag_name_len + 2, strlen($match) - 2 * $tag_name_len - 5); 141 142 // parse content using Splitter component 143 $calls = p_get_instructions('<'.$this->tag_name_splitter.'>' 144 .$content.'</'.$this->tag_name_splitter.'>'); 145 // compose commands and abbreviations 146 list($commands, $abbrs) = $this->_genCommandsAndAbbrs($calls); 147 148 // compose internal specification of table 149 $framework = $this->_genFramework($commands); 150 151 return array( 152 'framework' => $framework, 153 'abbreviations' => $abbrs 154 ); 155 } 156 157 /** 158 * Create XHTML text. 159 * 160 * @param string $mode render mode; only 'xhtml' supported 161 * @param Doku_Renderer $renderer 162 * @param array $data data from handler 163 * @return bool 164 */ 165 function render ($mode, Doku_Renderer $renderer, $data) 166 { 167 if ($mode != 'xhtml') 168 return false; 169 170 // add generated code to document 171 $renderer->doc .= "\n".$this->_renderDiagram( 172 $data['framework'], $data['abbreviations']); 173 return true; 174 } 175 176 /** 177 * Compose commands and abbreviations from wiki calls. 178 * 179 * Supported abbreviation parameters: 180 * - border-color (CSS property) 181 * - background-color (CSS property) 182 * - text-align (CSS property) 183 * - padding (CSS property) 184 * 185 * @param array $calls DokuWiki calls 186 * @return array array($commands, $abbreviations) 187 */ 188 function _genCommandsAndAbbrs ($calls) 189 { 190 $diagram_entered = false; 191 $commands = array(); 192 $abbrs = array(); 193 $line_index = -1; 194 195 // handle call list 196 foreach ($calls as $call) 197 { 198 // get plugin related info from call 199 if ($call[0] == 'plugin' && $call[1][0] == 'diagram_splitter') 200 { 201 $data = $call[1][1]; 202 $diagram_call_state = $call[1][2]; 203 } 204 else 205 { 206 $data = null; 207 $diagram_call_state = 0; 208 } 209 210 // wait until entering to diagram 211 if (!$diagram_entered && $diagram_call_state != DOKU_LEXER_ENTER) 212 continue; 213 // just entered: skipping 214 if (!$diagram_entered) 215 { 216 $diagram_entered = true; 217 continue; 218 } 219 220 // exited from diagram: stop handling 221 if ($diagram_call_state == DOKU_LEXER_EXIT) 222 break; 223 224 // received newline: start new line 225 if ($diagram_call_state == DOKU_LEXER_MATCHED && $data['type'] == 'newline') 226 { 227 // avoid unset lines of commands 228 if ($line_index >= 0 && !array_key_exists($line_index, $commands)) 229 $commands[$line_index] = array (); 230 // increment index 231 $line_index++; 232 // stop catching calls for abbr 233 $abbr_met = false; 234 if (isset($catching_abbr)) 235 unset($catching_abbr); 236 continue; 237 } 238 // must receive first newline before processing commands 239 if ($line_index < 0) 240 continue; 241 242 // received command: add to line of commands 243 if ($diagram_call_state == DOKU_LEXER_MATCHED && $data['type'] == 'command') 244 { 245 // deny commands after first abbreviation in line 246 if (!$abbr_met) 247 $commands[$line_index][] = $data['command']; 248 // stop catching calls for last abbr 249 $abbr_met = false; 250 if (isset($catching_abbr)) 251 unset($catching_abbr); 252 continue; 253 } 254 255 // received abbreviation: add to line of abbrs and start catching calls 256 if ($diagram_call_state == DOKU_LEXER_MATCHED && $data['type'] == 'abbr eval') 257 { 258 $abbr_met = true; 259 $abbrs[$line_index][$data['abbr']]['content'] = array(); 260 $abbrs[$line_index][$data['abbr']]['params'] = array(); 261 // override some parameters by user values 262 if (isset ($data['params'])) 263 { 264 $params = explode(';', $data['params']); 265 foreach ($params as $param) 266 { 267 list ($key, $value) = explode(':', $param); 268 $key = trim ($key); 269 $value = trim ($value); 270 $is_valid = false; 271 switch ($key) 272 { 273 case 'border-color': 274 case 'background-color': 275 $is_valid = $this->_validateCSSColor ($value); 276 break; 277 case 'text-align': 278 $is_valid = $this->_validateCSSTextAlign ($value); 279 break; 280 case 'padding': 281 $is_valid = $this->_validateCSSPadding ($value); 282 break; 283 } 284 if ($is_valid) 285 $abbrs[$line_index][$data['abbr']]['params'][$key] = $value; 286 } 287 } 288 $catching_abbr = &$abbrs[$line_index][$data['abbr']]['content']; 289 continue; 290 } 291 292 // received raw unmatched text and catching is on: add cdata call to abbr 293 if ($diagram_call_state == DOKU_LEXER_UNMATCHED && isset($catching_abbr)) 294 { 295 $catching_abbr[] = array('cdata', array($data['text']), $call[2]); 296 continue; 297 } 298 299 // received arbitrary call and catching is on: add call to abbr 300 if (isset($catching_abbr)) 301 $catching_abbr[] = $call; 302 303 // skip everything else 304 } 305 306 // remove trailing garbage 307 for ($i = 0; $i < count($commands); $i++) 308 { 309 $line_length = count($commands[$i]); 310 // remove last element, if no abbreviations found, 311 // because last delimiter is a 'border' of the table 312 // do not care, if someone specified garbage after last delimiter 313 if ($line_length > 0 && !array_key_exists($i, $abbrs)) 314 unset($commands[$i][$line_length - 1]); 315 } 316 317 return array($commands, $abbrs); 318 } 319 320 /** 321 * Generate table's framework. 322 * 323 * Framework: array(row number => array (column number => cell spec)) 324 * + array('n_rows' => number of rows, 'n_cols' => number of columns). 325 * cell_spec: array( 326 * 'colspan' => colspan (optional), 327 * 'rowspan' => rowspan (optional), 328 * 'classes' => array(css class), 329 * 'text' => text for diagram block or abbreviation (optional), 330 * 'content' => raw xhtml code to paste into cell, if 'text' key isn't set (optional) 331 * ). 332 * 333 * @author Nikita Melnichenko [http://nikita.melnichenko.name] 334 * @author Olesya Melnichenko [http://melnichenko.name] 335 * 336 * @param array $commands specification scheme 337 * @return array 338 */ 339 function _genFramework ($commands) 340 { 341 // store number of rows 342 $res['n_rows'] = count($commands) * 2; 343 // number of columns is computed below 344 $res['n_cols'] = 0; 345 346 for ($i = 0, $ir = 0; $i < count($commands); $i++, $ir += 2) 347 { 348 for ($j = 0, $jr = 0; $j < count($commands[$i]); $j++) 349 { 350 // leading and trailing spaces are already ignored by splitter component 351 $cell_text = $commands[$i][$j]; 352 // split command to connection and arrow commands 353 list($conn_command, $arrow_command) = $this->_splitCommand($cell_text); 354 // 2x2 connection specs for current command 355 $conn_cells = null; 356 357 switch ($conn_command) 358 { 359 // === empty === 360 361 case "": 362 $conn_cells = $this->_connectionCells('nnnn', $arrow_command); 363 break; 364 365 // === solid or dashed lines === 366 367 // + + + 368 // 369 // 370 // 371 // 372 // 373 // + +-----+ 374 // | 375 // | 376 // | 377 // | 378 // | 379 // + + + 380 case ",": 381 $conn_cells = $this->_connectionCells('nssn', $arrow_command); 382 break; 383 case "F": 384 $conn_cells = $this->_connectionCells('nddn', $arrow_command); 385 break; 386 387 // + + + 388 // 389 // 390 // 391 // 392 // 393 // +-----+ + 394 // | 395 // | 396 // | 397 // | 398 // | 399 // + + + 400 case ".": 401 $conn_cells = $this->_connectionCells('nnss', $arrow_command); 402 break; 403 case "7": 404 $conn_cells = $this->_connectionCells('nndd', $arrow_command); 405 break; 406 407 // + + + 408 // 409 // 410 // 411 // 412 // 413 // +-----+-----+ 414 // | 415 // | 416 // | 417 // | 418 // | 419 // + + + 420 case "v": 421 $conn_cells = $this->_connectionCells('nsss', $arrow_command); 422 break; 423 case "V": 424 $conn_cells = $this->_connectionCells('nddd', $arrow_command); 425 break; 426 427 // + + + 428 // | 429 // | 430 // | 431 // | 432 // | 433 // + + + 434 // | 435 // | 436 // | 437 // | 438 // | 439 // + + + 440 case "!": 441 $conn_cells = $this->_connectionCells('snsn', $arrow_command); 442 break; 443 case ":": 444 $conn_cells = $this->_connectionCells('dndn', $arrow_command); 445 break; 446 447 // + + + 448 // | 449 // | 450 // | 451 // | 452 // | 453 // +-----+-----+ 454 // | 455 // | 456 // | 457 // | 458 // | 459 // + + + 460 case "+": 461 $conn_cells = $this->_connectionCells('ssss', $arrow_command); 462 break; 463 case "%": 464 $conn_cells = $this->_connectionCells('dddd', $arrow_command); 465 break; 466 467 // + + + 468 // 469 // 470 // 471 // 472 // 473 // +-----+-----+ 474 // 475 // 476 // 477 // 478 // 479 // + + + 480 case "-": 481 $conn_cells = $this->_connectionCells('nsns', $arrow_command); 482 break; 483 case "~": 484 $conn_cells = $this->_connectionCells('ndnd', $arrow_command); 485 break; 486 487 // + + + 488 // | 489 // | 490 // | 491 // | 492 // | 493 // + +-----+ 494 // 495 // 496 // 497 // 498 // 499 // + + + 500 case "`": 501 $conn_cells = $this->_connectionCells('ssnn', $arrow_command); 502 break; 503 case "L": 504 $conn_cells = $this->_connectionCells('ddnn', $arrow_command); 505 break; 506 507 // + + + 508 // | 509 // | 510 // | 511 // | 512 // | 513 // +-----+ + 514 // 515 // 516 // 517 // 518 // 519 // + + + 520 case "'": 521 $conn_cells = $this->_connectionCells('snns', $arrow_command); 522 break; 523 case "J": 524 $conn_cells = $this->_connectionCells('dnnd', $arrow_command); 525 break; 526 527 // + + + 528 // | 529 // | 530 // | 531 // | 532 // | 533 // +-----+-----+ 534 // 535 // 536 // 537 // 538 // 539 // + + + 540 case "^": 541 $conn_cells = $this->_connectionCells('ssns', $arrow_command); 542 break; 543 case "A": 544 $conn_cells = $this->_connectionCells('ddnd', $arrow_command); 545 break; 546 547 // + + + 548 // | 549 // | 550 // | 551 // | 552 // | 553 // +-----+ + 554 // | 555 // | 556 // | 557 // | 558 // | 559 // + + + 560 case "(": 561 $conn_cells = $this->_connectionCells('snss', $arrow_command); 562 break; 563 case "C": 564 $conn_cells = $this->_connectionCells('dndd', $arrow_command); 565 break; 566 567 // + + + 568 // | 569 // | 570 // | 571 // | 572 // | 573 // + +-----+ 574 // | 575 // | 576 // | 577 // | 578 // | 579 // + + + 580 case ")": 581 $conn_cells = $this->_connectionCells('sssn', $arrow_command); 582 break; 583 case "D": 584 $conn_cells = $this->_connectionCells('dddn', $arrow_command); 585 break; 586 587 // === mixed lines === 588 589 // + + + 590 // 591 // 592 // 593 // 594 // 595 // +- - -+- - -+ 596 // | 597 // | 598 // | 599 // | 600 // | 601 // + + + 602 case "y": 603 $conn_cells = $this->_connectionCells('ndsd', $arrow_command); 604 break; 605 606 // + + + 607 // | 608 // 609 // | 610 // 611 // | 612 // +-----+-----+ 613 // | 614 // 615 // | 616 // 617 // | 618 // + + + 619 case "*": 620 $conn_cells = $this->_connectionCells('dsds', $arrow_command); 621 break; 622 623 // + + + 624 // | 625 // 626 // | 627 // 628 // | 629 // + +-----+ 630 // | 631 // 632 // | 633 // 634 // | 635 // + + + 636 case "}": 637 $conn_cells = $this->_connectionCells('dsdn', $arrow_command); 638 break; 639 640 // + + + 641 // | 642 // 643 // | 644 // 645 // | 646 // +-----+ + 647 // | 648 // 649 // | 650 // 651 // | 652 // + + + 653 case "{": 654 $conn_cells = $this->_connectionCells('dnds', $arrow_command); 655 break; 656 657 // + + + 658 // | 659 // | 660 // | 661 // | 662 // | 663 // + +- - -+ 664 // | 665 // | 666 // | 667 // | 668 // | 669 // + + + 670 case "]": 671 $conn_cells = $this->_connectionCells('sdsn', $arrow_command); 672 break; 673 674 // + + + 675 // | 676 // | 677 // | 678 // | 679 // | 680 // +- - -+ + 681 // | 682 // | 683 // | 684 // | 685 // | 686 // + + + 687 case "[": 688 $conn_cells = $this->_connectionCells('snsd', $arrow_command); 689 break; 690 691 // + + + 692 // | 693 // | 694 // | 695 // | 696 // | 697 // +- - -+- - -+ 698 // 699 // 700 // 701 // 702 // 703 // + + + 704 case "h": 705 $conn_cells = $this->_connectionCells('sdnd', $arrow_command); 706 break; 707 708 // + + + 709 // | 710 // | 711 // | 712 // | 713 // | 714 // +- - -+- - -+ 715 // | 716 // | 717 // | 718 // | 719 // | 720 // + + + 721 case "#": 722 $conn_cells = $this->_connectionCells('sdsd', $arrow_command); 723 break; 724 725 // + + + 726 // 727 // 728 // 729 // 730 // 731 // +-----+-----+ 732 // | 733 // 734 // | 735 // 736 // | 737 // + + + 738 case "p": 739 $conn_cells = $this->_connectionCells('nsds', $arrow_command); 740 break; 741 742 // + + + 743 // | 744 // 745 // | 746 // 747 // | 748 // +-----+-----+ 749 // 750 // 751 // 752 // 753 // 754 // + + + 755 case "b": 756 $conn_cells = $this->_connectionCells('dsns', $arrow_command); 757 break; 758 759 // === box === 760 761 default: 762 $res[$ir][$jr] = $this->_boxCell(6, 2, $cell_text); 763 $jr += 6; 764 } 765 766 // apply connection cells to the result 767 if (!is_null($conn_cells)) 768 { 769 // we must have a proper order of creation of elements of framework, do not use list() here 770 $res[$ir][$jr] = $conn_cells[0]; 771 $res[$ir][$jr + 1] = $conn_cells[1]; 772 $res[$ir + 1][$jr] = $conn_cells[2]; 773 $res[$ir + 1][$jr + 1] = $conn_cells[3]; 774 $jr += 2; 775 } 776 } 777 778 // compute number of columns 779 if ($res['n_cols'] < $jr) 780 $res['n_cols'] = $jr; 781 } 782 783 return $res; 784 } 785 786 /** 787 * Split command to connection part and arrow part. 788 * 789 * @param string $command 790 * @return array array($connection_command, $arrow_command) 791 */ 792 function _splitCommand ($command) 793 { 794 $command_parts = explode('@', $command, 2); 795 if (!isset($command_parts[1]) || !preg_match("/^[0-9a-f]{1,2}$/i", $command_parts[1])) 796 $command_parts[1] = 0; 797 else 798 { 799 // convert to bits: 'a' -> '0xa', 'ab' -> '0xba' 800 // see docs and params of _connectionCells 801 $v = $command_parts[1]; 802 if (strlen($v) == 2) 803 $v = $v[1].$v[0]; 804 $command_parts[1] = intval($v, 16); 805 } 806 return $command_parts; 807 } 808 809 /** 810 * Generate box cell spec. 811 * 812 * Box is an entity with wiki text. 813 * 814 * @param integer $width colspan 815 * @param integer $height rowspan 816 * @param string $text box text or abbreviation 817 * @param string $border css border 818 * @param string $background_color css color 819 * @return array cell spec 820 */ 821 function _boxCell ($width, $height, $text) 822 { 823 return array( 824 'colspan' => $width, 825 'rowspan' => $height, 826 'classes' => array($this->css_classes['block']), 827 'text' => $text 828 ); 829 } 830 831 /** 832 * Generate 2x2 pattern of connections cells. 833 * 834 * Each connection cell provides connection lines using its borders. 835 * They could also contain divs with arrowheads. 836 * 837 * @param string $border_spec 4 chars containing line type in top, right, bottom, left directions; 838 * line type chars are: 's' for solid, 'd' for dashed, 'n' for no line 839 * @param int $arrow_spec 8 bits are used: 840 * the first 4 bits indicate if arrow exists (=1) or not (=0) in top, right, bottom, left directions, 841 * the next 4 bits indicate if arrowhead look inside (=1) or outside (=0) in top, right, bottom, left directions, 842 * @return array array(cell_{0,0}, cell_{0,1}, cell_{1,0}, cell_{1,1}) 843 */ 844 function _connectionCells ($border_spec, $arrow_spec) 845 { 846 // direction numbers: top (0), right (1), bottom (2), left (3) 847 // cell numbers: {0,0} -> 0, {0,1} -> 1, {1,0} -> 2, {1,1} -> 3 848 // + + + 849 // | 850 // 851 // cell 0 cell 852 // 0 1 853 // | 854 // +- 3 -+- 1 -+ 855 // | 856 // 857 // cell 2 cell 858 // 2 3 859 // | 860 // + + + 861 862 // init 863 for ($i = 0; $i < 4; $i++) 864 $cells[$i] = array('classes' => array()); 865 866 // fill borders 867 if ($border_spec[0] != 'n') 868 $cells[0]['classes'][] = $this->_borderClass($border_spec[0], 'right'); 869 if ($border_spec[1] != 'n') 870 $cells[1]['classes'][] = $this->_borderClass($border_spec[1], 'bottom'); 871 if ($border_spec[2] != 'n') 872 $cells[2]['classes'][] = $this->_borderClass($border_spec[2], 'right'); 873 if ($border_spec[3] != 'n') 874 $cells[0]['classes'][] = $this->_borderClass($border_spec[3], 'bottom'); 875 876 // div elements with arrows, direction to cell number mapping 877 // 0 -> 1, 1 -> 3. 2 -> 2, 3 -> 0 878 // + + + 879 // | 880 // 881 // 0 0>>1 882 // ^ 883 // ^ | 884 // +- 3 -+- 1 -+ 885 // | v 886 // v 887 // 2<<2 3 888 // 889 // | 890 // + + + 891 892 // fill primary arrow classes 893 if ($arrow_spec & (1 << 0)) 894 { 895 $cells[1]['classes'][] = $this->css_classes['arrow-top']; 896 $cells[1]['content'] = '<div />'; 897 } 898 if ($arrow_spec & (1 << 1)) 899 { 900 $cells[3]['classes'][] = $this->css_classes['arrow-right']; 901 $cells[3]['content'] = '<div />'; 902 } 903 if ($arrow_spec & (1 << 2)) 904 { 905 $cells[2]['classes'][] = $this->css_classes['arrow-bottom']; 906 $cells[2]['content'] = '<div />'; 907 } 908 if ($arrow_spec & (1 << 3)) 909 { 910 $cells[0]['classes'][] = $this->css_classes['arrow-left']; 911 $cells[0]['content'] = '<div />'; 912 } 913 // fill arrowhead direction 914 if ($arrow_spec & (1 << (0 + 4))) 915 $cells[1]['classes'][] = $this->css_classes['arrow-inside']; 916 if ($arrow_spec & (1 << (1 + 4))) 917 $cells[3]['classes'][] = $this->css_classes['arrow-inside']; 918 if ($arrow_spec & (1 << (2 + 4))) 919 $cells[2]['classes'][] = $this->css_classes['arrow-inside']; 920 if ($arrow_spec & (1 << (3 + 4))) 921 $cells[0]['classes'][] = $this->css_classes['arrow-inside']; 922 923 // clear 924 for ($i = 0; $i < 4; $i++) 925 if (empty($cells[$i]['classes'])) 926 unset($cells[$i]['classes']); 927 928 return $cells; 929 } 930 931 /** 932 * Generate border CSS class for connection cell. 933 * 934 * @param string $type 's' for solid, 'd' for dashed 935 * @param string $direction 'right'or 'bottom' 936 * @return string class name 937 */ 938 function _borderClass ($type, $direction) 939 { 940 if ($type != 's' && $type != 'd') 941 return 'error'; 942 $key = "border-$direction-".($type == 's' ? 'solid' : 'dashed'); 943 return $this->css_classes[$key]; 944 } 945 946 /** 947 * Generate table with diagram. 948 * 949 * @param array $framework table framework generated by _genFramework 950 * @param array $abbrs information about abbreviations 951 * @return string xhtml table 952 */ 953 function _renderDiagram ($framework, $abbrs) 954 { 955 $n_rows = $framework['n_rows']; 956 $n_cols = $framework['n_cols']; 957 958 // output table 959 $table = '<table class="diagram">'."\n"; 960 // create horizontal spacer row 961 // first cell is for column of vertical spacers 962 $table .= "\t<tr>\n\t\t<td></td>\n"; 963 for ($i = 0; $i < $n_cols; $i++) 964 $table .= "\t\t<td class=\"".$this->css_classes['spacer-horizontal']."\"><div /></td>\n"; 965 $table .= "\t</tr>\n"; 966 // create diagram rows 967 for ($i = 0; $i < $n_rows; $i++) 968 { 969 // get table row spec 970 $row = array_key_exists($i, $framework) ? $framework[$i] : array (); 971 // line number 972 $line_index = $i / 2; 973 974 // output tr 975 // first cell is for column of vertical spacers 976 $table .= "\t<tr>\n\t\t<td class=\"".$this->css_classes['spacer-vertical']."\"><div /></td>\n"; 977 foreach ($row as $cell) 978 { 979 // generate cell content and update style 980 $cell_content = ''; 981 // empty cell or connection cell 982 if (!isset($cell['text'])) 983 { 984 if (isset($cell['content'])) 985 $cell_content = $cell['content']; 986 } 987 // cell with abbreviation 988 else if (array_key_exists($line_index, $abbrs) && array_key_exists($cell['text'], $abbrs[$line_index])) 989 { 990 $cell_content = $this->_renderWikiCalls ($abbrs[$line_index][$cell['text']]['content']); 991 $cell['style'] = $this->_generateBlockStyle ($abbrs[$line_index][$cell['text']]['params']); 992 } 993 // cell with unrecognized abbreviation 994 else 995 $cell_content = $cell['text']; 996 997 // output td 998 $table .= "\t\t<td" 999 .(isset($cell['classes']) && !empty($cell['classes']) ? ' class="'.implode(' ', $cell['classes']).'"' : '') 1000 .($cell['style'] != '' ? ' style="'.$cell["style"].'"' : '') 1001 .(isset($cell['colspan']) ? ' colspan="'.$cell["colspan"].'"' : '') 1002 .(isset($cell['rowspan']) ? ' rowspan="'.$cell["rowspan"].'"' : '') 1003 .'>' 1004 .$cell_content 1005 ."</td>\n"; 1006 } 1007 $table .= "\t</tr>\n"; 1008 } 1009 $table .= "</table>\n"; 1010 1011 return $table; 1012 } 1013 1014 /** 1015 * Generate CSS style for diagram block. 1016 * 1017 * @param array $params supported block CSS parameters 1018 * @return string css style 1019 */ 1020 function _generateBlockStyle ($params) 1021 { 1022 $css_props = array(); 1023 foreach ($params as $param => $value) 1024 $css_props[] = "$param: $value;"; 1025 return implode(' ', $css_props); 1026 } 1027 1028 /** 1029 * Render wiki instructions. 1030 * 1031 * @param array $calls DokuWiki calls 1032 * @return string xhtml markup 1033 */ 1034 function _renderWikiCalls ($calls) 1035 { 1036 return p_render('xhtml', $calls, $info); 1037 } 1038 1039 /** 1040 * Check if given color will not break css style. 1041 * 1042 * @param string $color checked string 1043 * @return true, if string is good for css 1044 */ 1045 function _validateCSSColor ($color) 1046 { 1047 // color name; for ex. 'green' 1048 if (preg_match("/^[a-z]+$/", $color)) 1049 return true; 1050 // short number notation; for ex. '#e73' 1051 if (preg_match("/^#[0-9a-fA-F]{3}$/", $color)) 1052 return true; 1053 // full number notation; for ex. '#ef703f' 1054 if (preg_match("/^#[0-9a-fA-F]{6}$/", $color)) 1055 return true; 1056 // rgb notation; for ex. 'rgb(11,22,33)' or 'rgb(11%,22%,33%)' 1057 if (preg_match("/^rgb\([ ]*[0-9]{1,3}[ ]*,[ ]*[0-9]{1,3}[ ]*,[ ]*[0-9]{1,3}[ ]*\)$/", $color)) 1058 return true; 1059 if (preg_match("/^rgb\([ ]*[0-9]{1,3}%[ ]*,[ ]*[0-9]{1,3}%[ ]*,[ ]*[0-9]{1,3}%[ ]*\)$/", $color)) 1060 return true; 1061 return false; 1062 } 1063 1064 /** 1065 * Check if given value is proper for css text-align. 1066 * 1067 * @param string $value checked string 1068 * @return true, if string is good as a value for css text-align 1069 */ 1070 function _validateCSSTextAlign ($value) 1071 { 1072 return $value == 'center' || $value == 'justify' || $value == 'left' || $value == 'right'; 1073 } 1074 1075 /** 1076 * Check if given value is proper for css padding. 1077 * 1078 * @param string $value checked string 1079 * @return true, if string is good as a value for css padding 1080 */ 1081 function _validateCSSPadding ($value) 1082 { 1083 if (preg_match("/^((auto|[0-9]+px|[0-9]+%|[0-9]+em)[ ]*){1,4}$/", $value)) 1084 return true; 1085 return false; 1086 } 1087} 1088