1<?php 2/* 3 * This file is part of the clockoon/dokuwiki-commonmark-plugin package. 4 * 5 * (c) Sungbin Jeon <clockoon@gmail.com> 6 * 7 * Original code based on the followings: 8 * - CommonMark JS reference parser (https://bitly.com/commonmark-js) (c) John MacFarlane 9 * - league/commonmark (https://github.com/thephpleague/commonmark) (c) Colin O'Dell <colinodell@gmail.com> 10 * 11 * For the full copyright and license information, please view the LICENSE 12 * file that was distributed with this source code. 13 */ 14 15 require_once __DIR__.'/src/bootstrap.php'; 16 17 use Dokuwiki\Plugin\Commonmark\Commonmark; 18 use Nette\Utils\Strings; 19 20 if(!defined('DOKU_INC')) die(); 21 22 class action_plugin_commonmark extends DokuWiki_Action_Plugin { 23 // array for heading positions 24 // [hid] => {[depth], [startline], [endline]} 25 public $headingInfo = []; 26 // positions of newline 27 public $linePosition = []; 28 // flag for checking first run only 29 public $firstRun = true; 30 31 /** 32 * pass text to Commonmark parser before DW parser 33 */ 34 public function register(Doku_Event_Handler $controller) { 35 $controller->register_hook('PARSER_WIKITEXT_PREPROCESS', 'BEFORE', $this, 36 '_commonmarkparse'); 37 $controller->register_hook('HTML_SECEDIT_BUTTON', 'BEFORE', $this, 38 '_editbutton'); 39 } 40 41 public function _editbutton(Doku_Event $event, $param) { 42 //echo(print_r($this->headingInfo)); 43 //echo(print_r($this->linePosition)); 44 global $conf; 45 46 // get hid 47 $hid = $event->data['hid']; 48 // fetch range on original md 49 // check hid match 50 $keys = array_keys($this->headingInfo); 51 if (in_array($hid,$keys)) { 52 // get max section editing level config 53 $maxsec = $conf['maxseclevel']; 54 // set start position 55 // first, check whether first heading 56 if ($hid == $keys[0]) { 57 $start = 1; 58 } else { 59 $lineStart = $this->headingInfo[$hid]['startline'] - 1; 60 // since CommonMark library finds heading marks, we have to declare 61 $start = $this->linePosition[$lineStart] + 1; 62 } 63 // find end key & location; proceed while max level or less arrived 64 $endlevel = 52; 65 $index = array_search($hid,$keys); 66 $end = 0; // 0 means end of document 67 $stop = false; 68 while($stop == false) { 69 if (isset($keys[$index+1])) { // check for non-last element 70 $endlevel = $this->headingInfo[$keys[$index+1]]['level']; 71 $lineEnd = $this->headingInfo[$keys[$index+1]]['startline'] - 1; // go one line up 72 $end = $this->linePosition[$lineEnd]; 73 if($maxsec>=$endlevel) { $stop = true; } 74 } else { 75 $end = 0; 76 $stop = true; 77 } 78 $index = $index + 1; 79 } 80 if($end == 0) { 81 $event->data['range'] = (string)$start.'-'; 82 } else { 83 $event->data['range'] = (string)$start.'-'.$end; 84 } 85 86 } 87 // example: $event->data['range'] = '1-2'; 88 } 89 90 public function _commonmarkparse(Doku_Event $event, $param) { 91 $markdown = $event->data; 92 // check force_commonmark option; if 1, ignore doctype 93 if ($this->getConf('force_commonmark')) { 94 $markdown = ltrim($markdown); 95 $result = Commonmark::RendtoDW($markdown, $this->getConf('frontmatter_tag')); 96 } 97 elseif (preg_match('/\A<!DOCTYPE markdown>/',$markdown)) { 98 $markdown = preg_replace('/\A<!DOCTYPE markdown>\n/','',$markdown); 99 $markdown = ltrim($markdown); 100 $result = Commonmark::RendtoDW($markdown, $this->getConf('frontmatter_tag')); 101 $event->data = $result['text']; 102 } 103 $event->data = $result['text']; 104 if ($this->firstRun == true) { 105 // get position of each line 106 $lastPos = 0; 107 $this->linePosition[] = $lastPos; 108 while(($lastPos = strpos($markdown,PHP_EOL,$lastPos)) !== false){ 109 $this->linePosition[] = $lastPos; 110 $lastPos = $lastPos + strlen(PHP_EOL); 111 } 112 $this->headingInfo = $this->CleanHeadingInfo($result['heading']); 113 $this->FixHeadingLine($markdown); 114 $this->firstRun = false; 115 } 116 } 117 118 public function CleanHeadingInfo(array $input): array { 119 $keys = array_keys($input); 120 foreach($keys as $key) { 121 $check = false; 122 $new_key = sectionId($key, $check); 123 if($new_key != $key) { 124 $input[$new_key] = $input[$key]; 125 unset($input[$key]); 126 } 127 } 128 uasort($input, fn($a, $b) => $a['startline'] <=> $b['startline']); 129 return $input; 130 } 131 132 public function FixHeadingLine(string $markdown) { 133 $arr = explode(PHP_EOL, $markdown); 134 foreach($this->headingInfo as &$element) { 135 $target = $arr[$element['startline'] - 1]; 136 if (preg_match('/^#{1,6}(?:[ \t]+|$)/', $target) == 1) { 137 $element['endline'] = $element['startline']; 138 } else { 139 $element['startline'] = $element['startline'] - 1; 140 $element['endline'] = $element['endline'] - 1; 141 } 142 } 143 } 144} 145