isParsed) { return false; } $this->isParsed = true; $this->renderPos = strlen($this->renderer->doc); return $this->parse($content); } private function getRenderResult($escapedPos = null) { if ($escapedPos === null) { $renderPos = $this->renderPos; } else { $renderPos = $escapedPos; } $result = substr($this->renderer->doc, $renderPos); $this->renderPos = strlen($this->renderer->doc); return $result; } protected function collectText($blocks) { $result = ''; foreach ($blocks as $block) { if ($block[0] == 'text') { $result .= $block[1]; } } return $result; } // Parser protected function renderParagraph($block) { $escapedPos = $this->renderPos; $this->renderer->p_open(); $this->renderAbsy($block['content']); $this->renderer->p_close(); return $this->getRenderResult($escapedPos); } // Markdown protected function renderText($block) { $contentLines = preg_split('/ +\n/', $block[1]); $first = true; foreach ($contentLines as $contentLine) { if ($first) { $first = false; } else { $this->renderer->linebreak(); } $this->renderer->cdata(html_entity_decode($contentLine)); } return $this->getRenderResult(); } // block\CodeTrait protected function renderCode($block) { $lang = null; if (array_key_exists('language', $block)) { $lang = $block['language']; } $this->renderer->code($block['content'], $lang); return $this->getRenderResult(); } // block\HeadlineTrait protected function renderHeadline($block) { $content = $this->collectText($block['content']); $this->renderer->header(html_entity_decode($content), $block['level'], $this->rendererContext['pos']); return $this->getRenderResult(); } // block\HtmlTrait private function isCommentOnlyXMLString($content) { if (preg_match('/^\s*\s*$/', $content)) { return true; } return false; } // Note: Fallback html rendering for DokuWiki 2018-04-22a // // See https://github.com/splitbrain/dokuwiki/issues/2563 // We should fallback for DokuWiki 2018-04-22a to avoid `Function create_function() is deprecated` private function isGeshiFallbackVersion() { return phpversion() >= '7.2' && substr($this->getDokuWikiVersion(), 0, 10) == '2018-04-22'; } protected function renderHtml($block) { $content = $block['content']."\n"; if ($this->isCommentOnlyXMLString($content)) { return ''; } global $conf; if ($this->isGeshiFallbackVersion() && !$conf['htmlok']) { $this->renderer->monospace_open(); $this->renderer->cdata($content); $this->renderer->monospace_close(); } else { $this->renderer->htmlblock($content); } return $this->getRenderResult(); } protected function renderInlineHtml($block) { $content = $block[1]; if ($this->isCommentOnlyXMLString($content)) { return ''; } global $conf; if ($this->isGeshiFallbackVersion() && !$conf['htmlok']) { $this->renderer->monospace_open(); $this->renderer->cdata($content); $this->renderer->monospace_close(); } else { $this->renderer->html($content); } return $this->getRenderResult(); } // block\ListTrait protected function renderList($block) { $escapedPos = $this->renderPos; if ($block['list'] == 'ol') { $this->renderer->listo_open(); } else { $this->renderer->listu_open(); } foreach ($block['items'] as $item => $itemLines) { $this->renderer->listitem_open($this->listLevel); $this->listLevel = $this->listLevel + 1; $this->renderer->listcontent_open(); $this->renderAbsy($itemLines); $this->renderer->listcontent_close(); $this->listLevel = $this->listLevel - 1; $this->renderer->listitem_close(); } if ($block['list'] == 'ol') { $this->renderer->listo_close(); } else { $this->renderer->listu_close(); } return $this->getRenderResult($escapedPos); } // block\QuoteTrait protected function renderQuote($block) { $escapedPos = $this->renderPos; $this->renderer->quote_open(); $this->renderAbsy($block['content']); $this->renderer->quote_close(); return $this->getRenderResult($escapedPos); } // block\RuleTrait protected function renderHr($block) { $this->renderer->hr(); return $this->getRenderResult(); } // block\TableTrait protected function renderTable($block) { $escapedPos = $this->renderPos; $this->renderer->table_open(); $cols = $block['cols']; $first = true; foreach ($block['rows'] as $row) { if ($first) { $first = false; $this->renderer->tablethead_open(); foreach ($row as $c => $cell) { $align = empty($cols[$c]) ? null : $cols[$c]; $this->renderer->tableheader_open(1, $align); $this->renderAbsy($cell); $this->renderer->tableheader_close(); } $this->renderer->tablethead_close(); continue; } $this->renderer->tablerow_open(); foreach ($row as $c => $cell) { $align = empty($cols[$c]) ? null : $cols[$c]; $this->renderer->tablecell_open(1, $align); $this->renderAbsy($cell); $this->renderer->tablecell_close(); } $this->renderer->tablerow_close(); } $this->renderer->table_close(); return $this->getRenderResult($escapedPos); } // inline\CodeTrait protected function renderInlineCode($block) { $this->renderer->monospace_open(); $this->renderer->cdata($block[1]); $this->renderer->monospace_close(); return $this->getRenderResult(); } // inline\EmphStrongTrait protected function renderStrong($block) { $escapedPos = $this->renderPos; $this->renderer->strong_open(); $this->renderAbsy($block[1]); $this->renderer->strong_close(); return $this->getRenderResult($escapedPos); } protected function renderEmph($block) { $escapedPos = $this->renderPos; $this->renderer->emphasis_open(); $this->renderAbsy($block[1]); $this->renderer->emphasis_close(); return $this->getRenderResult($escapedPos); } // inline\LinkTrait protected function renderEmail($block) { $this->renderer->emaillink($block[1]); return $this->getRenderResult(); } protected function renderUrl($block) { $this->renderer->externallink($block[1]); return $this->getRenderResult(); } abstract protected function lookupReference($key); abstract protected function parseInline($line); private function lookupRefKeyWithFallback($prefix, $block) { if (!isset($block['refkey'])) { return $block; } if (($ref = $this->lookupReference($block['refkey'])) !== false) { return array_merge($block, $ref); } $prefix_len = strlen($prefix); if (strncmp($block['orig'], $prefix, $prefix_len) === 0) { $this->renderer->cdata($prefix); $this->renderAbsy($this->parseInline(substr($block['orig'], $prefix_len))); } else { $this->renderer->cdata($block['orig']); } return false; } /** * Note: Avoid License Conflicting for Links with Titles * * DokuWiki is not supported links with titles, but Markdown is supported it. * We decided not to support links with titles before 2.1.0. However, since * many users voting the feature, we support it from 2.2.0. * * The simple way to support links with titles is copying methods from DokuWiki * and modifying. However, DokuWiki is licensed under the GPL-2.0-or-later and * this plugin is licensed under the Apache-2.0 OR GPL-2.0-or-later. So, we cannot * use parts of DokuWiki's source codes. Therefore, we use dangerous operations to * support links with titles for user needs. Be careful for this feature. * * Ref: https://github.com/mizunashi-mana/dokuwiki-plugin-mdpage/issues/35 */ protected function renderLink($block) { $escapedPos = $this->renderPos; if (($block = $this->lookupRefKeyWithFallback('[', $block)) === false) { return $this->getRenderResult($escapedPos); } // See https://github.com/splitbrain/dokuwiki/blob/cbaf278c50e5baf946b3bd606c369735fe0953be/inc/parser/handler.php#L527 $url = $block['url']; $text = $this->collectText($block['text']); $title = $block['title']; if (link_isinterwiki($url)) { // Interwiki $interwiki = explode('>', $url, 2); $this->renderDokuWikiInterwikiLink($url, $text, strtolower($interwiki[0]), $interwiki[1], $title); } elseif (preg_match('#^([a-z0-9\-\.+]+?)://#i', $url)) { // external link (accepts all protocols) $this->renderDokuWikiExternalLink($url, $text, $title); } elseif (preg_match('!^#.+!', $url)) { // local link $this->renderDokuWikiLocalLink(substr($url, 1), $text, $title); } else { // internal link $this->renderDokuWikiInternalLink($url, $text, $title); } return $this->getRenderResult($escapedPos); } private function renderDokuWikiInterwikiLink($match, $name, $wikiName, $wikiUri, $title = null) { $escapedPos = $this->renderPos; $this->renderer->interwikilink($match, $name, $wikiName, $wikiUri); if ($title === null) { return; } // See the note "Avoid License Conflicting for Links with Titles" $renderedContent = substr($this->renderer->doc, $escapedPos); $replacedContent = $this->replaceDokuWikiLinkTitle($renderedContent, $title); $this->renderer->doc = substr_replace($this->renderer->doc, $replacedContent, $escapedPos); } private function renderDokuWikiExternalLink($url, $name, $title = null) { $escapedPos = $this->renderPos; $this->renderer->externallink($url, $name); if ($title === null) { return; } // See the note "Avoid License Conflicting for Links with Titles" $renderedContent = substr($this->renderer->doc, $escapedPos); $replacedContent = $this->replaceDokuWikiLinkTitle($renderedContent, $title); $this->renderer->doc = substr_replace($this->renderer->doc, $replacedContent, $escapedPos); } private function renderDokuWikiLocalLink($hash, $name, $title = null) { $escapedPos = $this->renderPos; $this->renderer->locallink($hash, $name); if ($title === null) { return; } // See the note "Avoid License Conflicting for Links with Titles" $renderedContent = substr($this->renderer->doc, $escapedPos); $replacedContent = $this->replaceDokuWikiLinkTitle($renderedContent, $title); $this->renderer->doc = substr_replace($this->renderer->doc, $replacedContent, $escapedPos); } private function renderDokuWikiInternalLink($id, $name, $title = null) { $escapedPos = $this->renderPos; $this->renderer->internallink($id, $name); if ($title === null) { return; } // See the note "Avoid License Conflicting for Links with Titles" $renderedContent = substr($this->renderer->doc, $escapedPos); $replacedContent = $this->replaceDokuWikiLinkTitle($renderedContent, $title); $this->renderer->doc = substr_replace($this->renderer->doc, $replacedContent, $escapedPos); } /** * Ref: https://github.com/splitbrain/dokuwiki/blob/release_stable_2020-07-29/inc/parser/xhtml.php#L1601. */ private function replaceDokuWikiLinkTitle($linkContent, $title) { $replacedTitle = strtr( htmlspecialchars($title), [ '>' => '%3E', '<' => '%3C', '"' => '%22', ] ); return preg_replace( '/]*) title="([^"]*)"([^>]*)>/', '', $linkContent ); } protected function renderImage($block) { $escapedPos = $this->renderPos; if (($block = $this->lookupRefKeyWithFallback('![', $block)) === false) { return $this->getRenderResult($escapedPos); } // See https://github.com/splitbrain/dokuwiki/blob/cbaf278c50e5baf946b3bd606c369735fe0953be/inc/parser/handler.php#L722 $url = $block['url']; $text = $block['text']; if (media_isexternal($url) || link_isinterwiki($url)) { $this->renderer->externalmedia($url, $text); } else { $this->renderer->internalmedia($url, $text); } return $this->getRenderResult($escapedPos); } // inline\StrikeoutTrait protected function renderStrike($block) { $escapedPos = $this->renderPos; $this->renderer->deleted_open(); $this->renderAbsy($block[1]); $this->renderer->deleted_close(); return $this->getRenderResult($escapedPos); } // inline\UrlLinkTrait protected function renderAutoUrl($block) { $this->renderer->externallink($block[1]); return $this->getRenderResult(); } }