This is an old revision of the document!
Table of Contents
yalist Plugin
Compatible with DokuWiki
Rincewind, Angua, Adora Belle, Weatherwax, "Ponder Stibbons", Hrun, Binky, detritus
This extension has not been updated in over 2 years. It may no longer be maintained or supported and may have compatibility issues.
Similar to definitionlist, definitions, deflist, dl, extlist, lists, mllist
Overview
There are many list syntax plugins (see the list of similar plugins above), but this one is mine. I created it because I am not happy with any of the others:
- Only lists and deflist allow paragraph breaks within list items, but their syntax is radically different from the standard DokuWiki syntax for lists. Worse, the syntax that each plugin introduces for list item terminators differs from the other.1)
- definitions and definitionlist do not allow nested definition lists.
My goal in creating yalist was to integrate definition lists into the existing nestable list handler, and to allow list items to contain multiple paragraphs, while maintaining syntax- and output-level compatibility with the existing list handler. yalist achieves this by preempting DokuWiki's list handler, replacing it with a new handler and renderer that render the standard list syntax exactly as DokuWiki would.
Syntax
- ordered list item [<ol><li>] <!-- as standard syntax --> * unordered list item [<ul><li>] <!-- as standard syntax --> ? definition list term [<dl><dt>] : definition list definition [<dl><dd>] -- ordered list item w/ multiple paragraphs ** unordered list item w/ multiple paragraphs :: definition list definition w/multiple paragraphs .. new paragraph in --, **, or ::
Lists can be nested within lists by indenting them further, just as in the standard DokuWiki syntax.
Example
The following DokuWiki source:
- Ordered list item 1 - Ordered list item 2 -- Ordered list item 3... .. ... in multiple paragraphs - Ordered list item 4 * Unordered list item ** Unordered list item... .. ... in multiple paragraphs - Ordered list, first level - Second level - Third level - Fourth level -- Back to second level - //Second?! What happened to third?// .. //Quiet, you.// - Back to first level - Still at first level ? Definition list : Definition lists vary only slightly from other types of lists in that list items consist of two parts: a term and a description. The term is given by the DT element and is restricted to inline content. The description is given with a DD element that contains block-level content. [Source: W3C] ? Definition list w/ multiple paragraphs :: The style sheet provided with this plugin will render these paragraphs... .. ... to the left of the term being defined. ? Definition list w/ multiple "paragraphs" : Another way to separate blocks of text in a definition... : ... is to simply have multiple definitions for a term (or group of terms). : This definition list has DD tags without any preceding DT tags. : Hey, it's legal XHTML. ? Just like DT tags without following DD tags. ?? But DT tags can't contain paragraphs. That would __not__ be legal XHTML. .. If you try, the result will be rendered oddly.
… is rendered in XHTML as follows:
<ol> <li class="level1"><div class="li"> Ordered list item 1 </div></li> <li class="level1"><div class="li"> Ordered list item 2 </div></li> <li class="level1"><div class="li"> <p> Ordered list item 3… </p><p> … in multiple paragraphs </p> </div></li> <li class="level1"><div class="li"> Ordered list item 4 </div></li> </ol> <ul> <li class="level1"><div class="li"> Unordered list item </div></li> <li class="level1"><div class="li"> <p> Unordered list item… </p><p> … in multiple paragraphs </p> </div></li> </ul> <ol> <li class="level1"><div class="li"> Ordered list, first level </div><ol> <li class="level2"><div class="li"> Second level </div><ol> <li class="level3"><div class="li"> Third level </div><ol> <li class="level4"><div class="li"> Fourth level </div></li> </ol> </li> </ol> </li> <li class="level2"><div class="li"> <p> Back to second level </p> </div><ol> <li class="level3"><div class="li"> <em>Second?! What happened to third?</em> </div></li> </ol> <div class="li"> <p> <em>Quiet, you.</em> </p> </div></li> </ol> </li> <li class="level1"><div class="li"> Back to first level </div></li> <li class="level1"><div class="li"> Still at first level </div></li> </ol> <dl> <dt class="level1"><span class="dt"> Definition list</span></dt> <dd class="level1"> Definition lists vary only slightly from other types of lists in that list items consist of two parts: a term and a description. The term is given by the DT element and is restricted to inline content. The description is given with a DD element that contains block-level content. [Source: <acronym title="World Wide Web Consortium">W3C</acronym>]</dd> <dt class="level1"><span class="dt"> Definition list w/ multiple paragraphs</span></dt> <dd class="level1"><p> The style sheet provided with this plugin will render these paragraphs… </p><p> … to the left of the term being defined. </p><dl> <dt class="level2"><span class="dt"> Definition list w/ multiple “paragraphs”</span></dt> <dd class="level2"> Another way to separate blocks of text in a definition…</dd> <dd class="level2"> … is to simply have multiple definitions for a term (or group of terms).</dd> </dl> </dd> </dl> <dl> <dd class="level1"> This definition list has DD tags without any preceding DT tags.</dd> <dd class="level1"> Hey, it's legal <acronym title="Extensible HyperText Markup Language">XHTML</acronym>.</dd> <dt class="level1"><span class="dt"> Just like DT tags without following DD tags.</span></dt> <dt class="level1"><span class="dt">? But DT tags can't contain paragraphs. That would <em class="u">not</em> be legal <acronym title="Extensible HyperText Markup Language">XHTML</acronym>. .. If you try, the result will be rendered oddly.</span></dt> </dl>
Installation
Usual procedure. Download the plugin from http://www.paranoiacs.org/~sluskyb/hacks/dokuwiki/yalist/ in .ZIP or .tar.gz format; unpack it manually or use the plugin manager; or copy and paste the source files below.
Source Code
syntax.php
- syntax.php
<?php /* * This plugin extends DokuWiki's list markup syntax to allow definition lists * and list items with multiple paragraphs. The complete syntax is as follows: * * * - ordered list item [<ol><li>] <!-- as standard syntax --> * * unordered list item [<ul><li>] <!-- as standard syntax --> * ? definition list term [<dl><dt>] * : definition list definition [<dl><dd>] * * -- ordered list item w/ multiple paragraphs * ** unordered list item w/ multiple paragraphs * :: definition list definition w/multiple paragraphs * .. new paragraph in --, **, or :: * * * Lists can be nested within lists, just as in the standard DokuWiki syntax. * * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) * @author Ben Slusky <sluskyb@paranoiacs.org> * */ if (!defined('DOKU_INC')) define('DOKU_INC',realpath(dirname(__FILE__).'/../../').'/'); if (!defined('DOKU_PLUGIN')) define('DOKU_PLUGIN',DOKU_INC.'lib/plugins/'); require_once(DOKU_PLUGIN.'syntax.php'); class syntax_plugin_yalist extends DokuWiki_Syntax_Plugin { var $stack = array(); function getInfo() { return array( 'author' => 'Ben Slusky', 'email' => 'sluskyb@paranoiacs.org', 'date' => '2007-11-02', 'name' => 'Simple universal list plugin', 'desc' => 'Extend DokuWiki list syntax to allow definition list and multiple paragraphs in a list entry', 'url' => 'http://www.dokuwiki.org/plugin:yalist', ); } function getType() { return 'container'; } function getSort() { return 9; // just before listblock (10) } function getPType() { return 'block'; } function getAllowedTypes() { return array('substition', 'protected', 'disabled', 'formatting'); } function connectTo($mode) { $this->Lexer->addEntryPattern('\n {2,}(?:--?|\*\*?|\?|::?)', $mode, 'plugin_yalist'); $this->Lexer->addEntryPattern('\n\t{1,}(?:--?|\*\*?|\?|::?)', $mode, 'plugin_yalist'); $this->Lexer->addPattern('\n {2,}(?:--?|\*\*?|\?|::?|\.\.)', 'plugin_yalist'); $this->Lexer->addPattern('\n\t{1,}(?:--?|\*\*?|\?|::?|\.\.)', 'plugin_yalist'); } function postConnect() { $this->Lexer->addExitPattern('\n', 'plugin_yalist'); } function handle($match, $state, $pos, &$handler) { $output = array(); $level = 0; switch ($state) { case DOKU_LEXER_ENTER: $frame = $this->_interpret_match($match); $level = $frame['level'] = 1; array_push($output, "${frame['list']}_open", "${frame['item']}_open", "${frame['item']}_content_open"); if ($frame['paras']) array_push($output, 'p_open'); array_push($this->stack, $frame); break; case DOKU_LEXER_EXIT: $close_content = true; while ($frame = array_pop($this->stack)) { // for the first frame we pop off the stack, we'll need to // close the content tag; for the rest it will have been // closed already if ($close_content) { if ($frame['paras']) array_push($output, 'p_close'); array_push($output, "${frame['item']}_content_close"); $close_content = false; } array_push($output, "${frame['item']}_close", "${frame['list']}_close"); } break; case DOKU_LEXER_MATCHED: $last_frame = end($this->stack); if (substr($match, -2) == '..') { // new paragraphs cannot be deeper than the current depth, // but they may be shallower $para_depth = count(explode(' ', str_replace("\t", ' ', $match))); $close_content = true; while ($para_depth < $last_frame['depth'] && count($this->stack) > 1) { if ($close_content) { if ($last_frame['paras']) array_push($output, 'p_close'); array_push($output, "${last_frame['item']}_content_close"); $close_content = false; } array_push($output, "${last_frame['item']}_close", "${last_frame['list']}_close"); array_pop($this->stack); $last_frame = end($this->stack); } if ($last_frame['paras']) { if ($close_content) // depth did not change array_push($output, 'p_close', 'p_open'); else array_push($output, "${last_frame['item']}_content_open", 'p_open'); } else { // let's just pretend we didn't match... $state = DOKU_LEXER_UNMATCHED; $output = $match; } break; } $curr_frame = $this->_interpret_match($match); if ($curr_frame['depth'] > $last_frame['depth']) { // going one level deeper $level = $last_frame['level'] + 1; if ($last_frame['paras']) array_push($output, 'p_close'); array_push($output, "${last_frame['item']}_content_close", "${curr_frame['list']}_open"); } else { // same depth, or getting shallower $close_content = true; // keep popping frames off the stack until we find a frame // that's at least as deep as this one, or until only the // bottom frame (i.e. the initial list markup) remains while ($curr_frame['depth'] < $last_frame['depth'] && count($this->stack) > 1) { // again, we need to close the content tag only for // the first frame popped off the stack if ($close_content) { if ($last_frame['paras']) array_push($output, 'p_close'); array_push($output, "${last_frame['item']}_content_close"); $close_content = false; } array_push($output, "${last_frame['item']}_close", "${last_frame['list']}_close"); array_pop($this->stack); $last_frame = end($this->stack); } // pull the last frame off the stack; // it will be replaced by the current frame array_pop($this->stack); $level = $last_frame['level']; if ($close_content) { if ($last_frame['paras']) array_push($output, 'p_close'); array_push($output, "${last_frame['item']}_content_close"); $close_content = false; } array_push($output, "${last_frame['item']}_close"); if ($curr_frame['list'] != $last_frame['list']) { // change list types array_push($output, "${last_frame['list']}_close", "${curr_frame['list']}_open"); } } // and finally, open tags for the new list item array_push($output, "${curr_frame['item']}_open", "${curr_frame['item']}_content_open"); if ($curr_frame['paras']) array_push($output, 'p_open'); $curr_frame['level'] = $level; array_push($this->stack, $curr_frame); break; case DOKU_LEXER_UNMATCHED: $output = $match; break; } return array('state' => $state, 'output' => $output, 'level' => $level); } function _interpret_match($match) { $tag_table = array( '*' => 'u_li', '-' => 'o_li', '?' => 'dt', ':' => 'dd', ); $tag = $tag_table[substr($match, -1)]; return array( 'depth' => count(explode(' ', str_replace("\t", ' ', $match))), 'list' => substr($tag, 0, 1) . 'l', 'item' => substr($tag, -2), 'paras' => (substr($match, -1) == substr($match, -2, 1)), ); } function render($mode, &$renderer, $data) { if ($mode != 'xhtml' && $mode != 'latex') return false; if ($data['state'] == DOKU_LEXER_UNMATCHED) { $renderer->doc .= $renderer->_xmlEntities($data['output']); return true; } foreach ($data['output'] as $i) { $markup = ''; if ($mode == 'xhtml') { switch ($i) { case 'ol_open': $markup = "<ol>\n"; break; case 'ol_close': $markup = "</ol>\n"; break; case 'ul_open': $markup = "<ul>\n"; break; case 'ul_close': $markup = "</ul>\n"; break; case 'dl_open': $markup = "<dl>\n"; break; case 'dl_close': $markup = "</dl>\n"; break; case 'li_open': $markup = "<li class=\"level${data['level']}\">"; break; case 'li_content_open': $markup = "<div class=\"li\">\n"; break; case 'li_content_close': $markup = "\n</div>"; break; case 'li_close': $markup = "</li>\n"; break; case 'dt_open': $markup = "<dt class=\"level${data['level']}\">"; break; case 'dt_content_open': $markup = "<span class=\"dt\">"; break; case 'dt_content_close': $markup = "</span>"; break; case 'dt_close': $markup = "</dt>\n"; break; case 'dd_open': $markup = "<dd class=\"level${data['level']}\">"; break; case 'dd_content_open': $markup = "<div class=\"dd\">\n"; break; case 'dd_content_close': $markup = "\n</div>"; break; case 'dd_close': $markup = "</dd>\n"; break; case 'p_open': $markup = "<p>\n"; break; case 'p_close': $markup = "\n</p>"; break; } } else { // $mode == 'latex' switch ($i) { case 'ol_open': $markup = "\\begin{enumerate}\n"; break; case 'ol_close': $markup = "\\end{enumerate}\n"; break; case 'ul_open': $markup = "\\begin{itemize}\n"; break; case 'ul_close': $markup = "\\end{itemize}\n"; break; case 'dl_open': $markup = "\\begin{description}\n"; break; case 'dl_close': $markup = "\\end{description}\n"; break; case 'li_open': $markup = "\item "; break; case 'li_content_open': break; case 'li_content_close': break; case 'li_close': $markup = "\n"; break; case 'dt_open': $markup = "\item["; break; case 'dt_content_open': break; case 'dt_content_close': break; case 'dt_close': $markup = "] "; break; case 'dd_open': break; case 'dd_content_open': break; case 'dd_content_close': break; case 'dd_close': $markup = "\n"; break; case 'p_open': $markup = "\n"; break; case 'p_close': $markup = "\n"; break; } } $renderer->doc .= $markup; } if ($data['state'] == DOKU_LEXER_EXIT) $renderer->doc .= "\n"; return true; } }
style.css
- style.css
/* plugin: yalist */ div.dokuwiki ul, div.dokuwiki ol { //line-height: 1em; } div.dokuwiki dl { //line-height: 1em; margin-bottom: 0.5em; } div.dokuwiki dt { clear: left; } div.dokuwiki .dt { margin-right: 1em; color: __text_alt__; font-weight: bold; max-width: 30%; float: left; } div.dokuwiki .dt, div.dokuwiki .dd, div.dokuwiki .li { margin-bottom: 0.33em; } div.dokuwiki dd { margin-left: 3em; } div.dokuwiki dl:after, div.dokuwiki dl dl:before, div.dokuwiki dl ol:before, div.dokuwiki dl ul:before { content: '.'; display: block; clear: left; width: 0; height: 0; visibility: hidden; } /* end plugin: yalist */
Discussion
Updated with bugfixes and enhancements. In the unlikely event that anyone downloaded this plugin already, try it again now. — Ben Slusky 2007-11-10 02:30
Great idea but seems to be buggy. Once I installed the plugin all bullets are treated as new lists. Basically if I have
* Item 1 * Item 2 * Item 3
The plugin will interpret them as if I wrote
* Item 1 * Item 2 * Item 3
Worse than that, it seems to break all \[\[ \]\] interwiki links
Bulleted lists and interwiki links both work for me… do you have a public site I can look at? — Ben Slusky 2007-11-24 23:39
Ben, this seems to be almost exactly the right solution, but there's one thing it doesn't do correctly. I want to write
* First level ** First level * Second level * Second level .. continuing the last first level point
but this doesn't work: the .. isn't interpreted, and just becomes part of the last second-level bullet. It seems that the continuation context of the first level is lost in the transition to the second level. I can continue a second level bullet, but to go back to the first level I have to start a new bullet.
Any chance of this being corrected any time soon? Thanks, Andrew.
Maybe theClearFloatOutdent plugin can do some good for this thingy you describe… It's just an idea so please don't hang me on it. HTH
— Mischa The Evil 2008/06/11 18:38
Fixed in the latest version. — Ben Slusky 2008-07-08 02:02
Works—thanks! Andrew
I also wanted to be able to use markup provided by DokuWiki Core (DWC) and some DokuWiki Plugins (DWP's) of the mode type container
3) but that wasn't possible due to the fact that yalist doesn't allow markup provided by DWC and DWP's which are of the mode type container
.
This can be changed easily by adding the container
-mode type as a new (additional) argument of the method getAllowedTypes()
, which itself returns an array of mode types that may be nested within yalist's own markup.
This change can be applied by modifying line 56 from
return array('substition', 'protected', 'disabled', 'formatting');
to
return array('container', 'substition', 'protected', 'disabled', 'formatting');
I haven't noticed any unwanted behaviour afterwards, but YMMV
— Mischa The Evil 2008/06/11 22:54
Hm… I think this would result in invalid XHTML, so it's not going in. Sorry. — Ben Slusky 2008-07-08 02:14
Updated on 8 July 2008 to add support for LaTeX output (thanks to Michael Strey) and fix a formatting error when “..” is shallower than the preceding list markup. — Ben Slusky 2008-07-28 02:25
Where would I modify the code to allow the plugin to interpret “#” as an ordered list (ideally in addition to “-”, but instead of will do too)? — Ashtagon 2011-Aug-8.
First, thanks for this great plugin. I've just added ODT support and created a diff using WinMerge. So now let's put this plugin on the ODT Initiative / ODT Render Support page. — Tobias Kandzia 2013/06/28 09:05
You need the following formatting styles in your template:
- “Nummerierung 1 X” for ordered list item (<ol><li>)
- “Aufzählung 1 X” for unordered list item (<ul><li>)
- “Aufzählung 2 X” for key or definition list term (<dl><dt>)
- “Aufzählung 3 X” for value or definition list definition (<dl><dd>)
(style names using a german OpenOffice, X = “Anfang”, “Fortsetzung”, “Ende”)
Btw: I have almost no experience using definition lists in OpenOffice. If there is a recommended way to do them, let me know. I used two styles for the definition list to control alternative indenting and e.g. bold font for the term.
254c254 < if ($mode != 'xhtml' && $mode != 'latex') --- > if ($mode != 'xhtml' && $mode != 'latex' && $mode != 'odt') 262c262,265 < foreach ($data['output'] as $i) { --- > $startOfList = $data['state'] == DOKU_LEXER_ENTER; > $endOfList = $data['state'] == DOKU_LEXER_EXIT; > $output = $data['output']; > foreach ($output as $i) { 316c319 < } else { // $mode == 'latex' --- > } else if ($mode == 'latex') { 353a357,432 > } > } else if ($mode == 'odt') { > switch ($i) { > case 'ol_open': > $this->style = "Numbering_20_1"; > $markup = '<text:list text:style-name="'.$this->style.'">'; > break; > case 'ul_open': > $this->style = "List_20_1"; > $markup = '<text:list text:style-name="'.$this->style.'">'; > break; > case 'dl_open': > $this->style = "List_20_2"; > $markup = '<text:list text:style-name="'.$this->style.'">'; > break; > case 'ol_close': > case 'ul_close': > case 'dl_close': > $markup = '</text:list>'; > if ($endOfList) { > $cut = strripos($renderer->doc,'<text:p text:style-name='); > $lastItem = substr($renderer->doc,$cut); > $renderer->doc = substr($renderer->doc,0,$cut); > $renderer->doc .= preg_replace('/(.*?text:style-name="'.$this->style.')(.*?)(">.*?)/','\1_20_End\3',$lastItem); > } > $this->listHeader = false; > break; > > case 'li_open': > $markup = '<text:list-item>'; > break; > case 'li_content_open': > if (array_search($i,$output) < count($output)-1) break; > $style = $this->style . ($startOfList ? '_20_Start' : '_20_Cont.'); > $markup = '<text:p text:style-name="'.$style.'">'; > break; > case 'li_content_close': > if (array_search($i,$output) > 0) break; > $markup = '</text:p>'; break; > case 'li_close': > $markup = '</text:list-item>'; > break; > > case 'dt_open': // unconditional: DT tags can't contain paragraphs. That would not be legal XHTML. > $markup = '<text:list-item>'; > break; > case 'dd_open': > if (in_array("dt_close",$output) || in_array("dd_close",$output)) break; // continue with paragraphs > $this->listHeader = $startOfList; > $markup = $this->listHeader ? '<text:list-header>' : '<text:list-item>'; > break; > case 'dt_content_open': > $this->style = "List_20_2"; > if (array_search($i,$output) < count($output)-1) break; > $style = $this->style . ($startOfList ? '_20_Start' : '_20_Cont.'); > $markup = '<text:p text:style-name="'.$style.'">'; > break; > case 'dd_content_open': > $this->style = "List_20_3"; > if (array_search($i,$output) < count($output)-1) break; > $style = $this->style . ($startOfList ? '_20_Start' : '_20_Cont.'); > $markup = '<text:p text:style-name="'.$style.'">'; > break; > case 'dt_content_close': > case 'dd_content_close': > if (array_search($i,$output) > 0) break; > $markup = '</text:p>'; break; > case 'dt_close': > case 'dd_close': > if (in_array("dd_open",$output)) break; // continue with paragraphs > $markup = $this->listHeader ? '</text:list-header>' : '</text:list-item>'; > $this->listHeader = false; > break; > > case 'p_open': $markup = '<text:p text:style-name="'.$this->style.'_20_Cont.">'; break; > case 'p_close': $markup = '</text:p>'; break;
— Tobias Kandzia 2013/06/28 09:05
Feature Request
I need very often line breaks (<br>) but not paragraph breaks (<p>), especially when inserting images inside lists. My current workaround is using \\
but it's not very readyble because of huge lines. Would it be somehow possible to have a line break syntax like e.g. .
at the beginning of the line?