1<?php
2/**
3 * Render Plugin for XHTML output with preserved linebreaks and markup in headers
4 *
5 * @author Max Westen <max@dlmax.org>
6 * @author Chris Smith <chris@jalakai.co.uk>
7 * @author Danny Lin <danny0838[at]pchome[dot]com[dot]tw>
8 */
9
10
11// must be run within Dokuwiki
12if(!defined('DOKU_INC')) die();
13
14/**
15 * All DokuWiki plugins to extend the parser/rendering mechanism
16 * need to inherit from this class
17 */
18class syntax_plugin_header3 extends DokuWiki_Syntax_Plugin {
19
20  function getType() { return 'baseonly';}
21  function getPType() { return 'block';}
22  function getAllowedTypes() { return array('formatting', 'substition', 'disabled'); }
23  function getSort() { return 49; }
24
25  /**
26   * Connect pattern to lexer
27   */
28  function connectTo($mode) {
29    $this->Lexer->addEntryPattern("[ \t]*={2,}(?=[^\n]+={2,}[ \t]*\n)", $mode, 'plugin_header3');
30  }
31
32  function postConnect() {
33    $this->Lexer->addExitPattern('={2,}[ \t]*(?=\n)', 'plugin_header3');
34  }
35
36  /**
37   * Handle the match
38   */
39  function handle($match, $state, $pos, Doku_Handler $handler){
40    switch ($state) {
41      case DOKU_LEXER_ENTER :
42        $this->h_level = 7 - strspn($match,"=");
43        $this->h_pos = $pos;
44        if ($handler->status['section']) $handler->_addCall('section_close',array(),$pos);
45        $handler->addPluginCall('header3',array($state),$state,$pos,$match);
46        $handler->CallWriter = & new Doku_Handler_Nest($handler->CallWriter,'nest_close');
47        return false;
48      case DOKU_LEXER_UNMATCHED :
49        $handler->_addCall('cdata', array($match), $pos);
50        return false;
51      case DOKU_LEXER_EXIT :
52        $handler->_addCall('nest_close', array(), $pos);
53        $handler->CallWriter->process();
54        $handler->CallWriter = & $handler->CallWriter->CallWriter;
55        $handler->addPluginCall('header3',array($state,$this->h_level,$this->h_pos),$state,$pos,$match);
56        $handler->_addCall('section_open',array($this->h_level),$pos);
57        $handler->status['section'] = true;
58        return false;
59    }
60    return false;
61  }
62
63  /**
64   * Create output
65   */
66  function render($format, Doku_Renderer $renderer, $data) {
67    list($state,$level,$pos) = $data;
68    switch ($state) {
69      case DOKU_LEXER_ENTER :
70        // store current parsed content
71        $this->store = $renderer->doc;
72        $renderer->doc  = '';
73        // metadata renderer should always parse content in the header
74        if ($format=='metadata') {
75          $this->capture = $renderer->capture;
76          $renderer->capture = true;
77        }
78        break;
79      case DOKU_LEXER_EXIT :
80        // retrieve content parsed by nest parser (i.e. in the header)
81        $title = trim($renderer->doc);
82        $renderer->doc = $this->store;
83        $this->store = '';
84        // restore variable
85        if ($format=='metadata') {
86          $renderer->capture = $this->capture;
87        }
88        // create header
89        if($level < 1) $level = 1;
90        $method = '_' . $format . '_header';
91        if (method_exists($this,$method)) {
92          // we have special procedure for the renderer
93          $this->$method($title, $level, $pos, $renderer);
94        } else {
95          // fall back to default renderer behavior
96          $renderer->header($title,$level,$pos);
97        }
98        break;
99    }
100    return true;
101  }
102
103  // simple function to strip all html tags
104  function _remove_html_tags($text) {
105    return preg_replace( "#<[^>]*?>#", "" ,  $text);
106  }
107
108  /**
109   * Revised procedures for renderers
110   *
111   * Basically adds &$renderer argument and replaces $this to $renderer,
112   * and our procedures of course.
113   */
114
115  function _xhtml_header($text, $level, $pos, &$renderer) {
116    global $conf;
117
118    $displaytext = $text;  // <= added
119    $text = htmlspecialchars_decode($this->_remove_html_tags($text),ENT_QUOTES);  // <= added
120    if(!$text) return; //skip empty headlines
121
122    $hid = $renderer->_headerToLink($text,true);
123
124    //only add items within configured levels
125    $renderer->toc_additem($hid, $text, $level);
126
127    // adjust $node to reflect hierarchy of levels
128    $renderer->node[$level-1]++;
129    if ($level < $renderer->lastlevel) {
130      for ($i = 0; $i < $renderer->lastlevel-$level; $i++) {
131        $renderer->node[$renderer->lastlevel-$i-1] = 0;
132      }
133    }
134    $renderer->lastlevel = $level;
135
136    if ($level <= $conf['maxseclevel'] &&
137        count($renderer->sectionedits) > 0 &&
138        $renderer->sectionedits[count($renderer->sectionedits) - 1][2] === 'section') {
139      $renderer->finishSectionEdit($pos - 1);
140    }
141
142    // write the header
143    $renderer->doc .= DOKU_LF.'<h'.$level;
144    if ($level <= $conf['maxseclevel']) {
145      $renderer->doc .= ' class="' . $renderer->startSectionEdit($pos, 'section', $text) . '"';
146    }
147    $renderer->doc .= '><a name="'.$hid.'" id="'.$hid.'">';
148    $renderer->doc .= $displaytext;  // <= revised
149    $renderer->doc .= "</a></h$level>".DOKU_LF;
150  }
151
152  function _odt_header($text, $level, $pos, &$renderer){
153    $displaytext = $text;  // <= added
154    $text = $this->_remove_html_tags($text);  // <= added
155    $hid = $renderer->_headerToLink($text,true);
156    $renderer->doc .= '<text:h text:style-name="Heading_20_'.$level.'" text:outline-level="'.$level.'">';
157    $renderer->doc .= '<text:bookmark-start text:name="'.$hid.'"/>';
158    $renderer->doc .= $displaytext;  // <= revised
159    $renderer->doc .= '<text:bookmark-end text:name="'.$hid.'"/>';
160    $renderer->doc .= '</text:h>';
161  }
162
163  function _xml_header($text, $level, $pos, &$renderer){
164    if (!$text) return; //skip empty headlines
165    $renderer->nextHeader  = '<header level="' . $level . '" pos="' . $pos . '">'.
166        $renderer->nextHeader .= $text;  // <= revised
167    $renderer->nextHeader .= '</header>'.DOKU_LF;
168  }
169}
170