- ordered list item [- ]
* unordered list item [
- ]
? definition list term [
- ]
: definition list definition [
- ]
-- 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 the 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 happend 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: **
-
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 happend 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.
===== 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:plugin|plugin manager]]; or copy and paste the source files below.
===== Source Code =====
==== syntax.php ====
]
* * unordered list item [- ]
* ? definition list term [
- ]
* : definition list definition [
- ]
*
* -- 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
*
*/
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 = "\n"; break;
case 'ol_close': $markup = "
\n"; break;
case 'ul_open': $markup = "\n"; break;
case 'ul_close': $markup = "
\n"; break;
case 'dl_open': $markup = "\n"; break;
case 'dl_close': $markup = "
\n"; break;
case 'li_open':
$markup = "- ";
break;
case 'li_content_open':
$markup = "\n";
break;
case 'li_content_close':
$markup = "\n";
break;
case 'li_close':
$markup = "
\n";
break;
case 'dt_open':
$markup = "- ";
break;
case 'dt_content_open':
$markup = "";
break;
case 'dt_content_close':
$markup = "";
break;
case 'dt_close':
$markup = "
\n";
break;
case 'dd_open':
$markup = "- ";
break;
case 'dd_content_open':
$markup = "\n";
break;
case 'dd_content_close':
$markup = "\n";
break;
case 'dd_close':
$markup = "
\n";
break;
case 'p_open': $markup = "\n"; break;
case 'p_close': $markup = "\n
"; 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 ====
/* 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. --- //[[sluskyb@paranoiacs.org|Ben Slusky]] 2007-11-10 02:30//
FIXME 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? --- //[[sluskyb@paranoiacs.org|Ben Slusky]] 2007-11-24 23:39//
FIXME 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 the
return array('substition', 'protected', 'disabled', 'formatting');
to
return array('container', 'substition', 'protected', 'disabled', 'formatting');
I haven't noticed any unwanted behaviour afterwards, but YMMV ;-)
--- //[[http://www.the-evil.pointclark.net|Mischa The Evil]] 2008/06/11 22:54//
> Hm... I think this would result in invalid XHTML, so it's not going in. Sorry. --- //[[sluskyb@paranoiacs.org|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. --- //[[sluskyb@paranoiacs.org|Ben Slusky]] 2008-07-28 02:25//