Difference between revisions of "Extension talk:Treeview5.php"

From Organic Design wiki
(Add bug for tracking down)
(new voodooless way)
Line 1: Line 1:
 
{{ext-talk-msg|Tree view}}
 
{{ext-talk-msg|Tree view}}
A more logical way of approaching a treeview in MediaWiki is for the PHP extension part to concentrate on allowing bullet lists (and maybe numbered lists too) to work recusively with transclusion, and for the tree rendering to be handled by existing JavaScript components such as [http://www.destroydrop.com/javascripts/tree/ dTree] which are very good and packed with features such as persistence between pages.
+
Treeview5.1 is a new approach to the core recursive bullet-list code which doesn't require any voodoo, and should be compatible with all MediaWiki versions including 1.12.
  
{{Note|[[Extension:DTree.php]] has been created in the mean time to test dtree, the code will be integrated into [[Extension:Treeview5.php]].}}
+
== How it works ==
 
+
The initial problem was how to write a parser-function which could generate a tree which could be composed of other transcluded tree's (i.e. parser-functions which are recursively executed forming a larger whole structure together). This seemed impossible since there are no MediaWiki properties available in the environment which reveal the current depth that the parser-function expansion is operating at.
[[Extension:Treeview4.php|Treeview4]] already handles the recursion aspect well and has been written in a much more modular way than [[Extension:Treeview.php|Treeview3]] allowing it to be independent of the tree rendering. But in this version I'd like to make the list-recursion work without any parser-function syntax at all - i.e. essentially to fix the MediaWiki parser's limitation of not allowing transclusion to work properly with lists as in the following example.
 
<pre>
 
*Foo
 
**Bar
 
**{{:Baz}}
 
**Fodda
 
</pre>
 
The new version would allow the Baz article (if it were a bullet list) to be properly nested at the correct level in the parent tree. This would simply be based on the fact that the transclusion is directly after an asterisk. The following example would not be indented, the indentation of <code>{{:Baz}}</code> is determined by the article itself.
 
<pre>
 
*Foo
 
**Bar
 
{{:Baz}}
 
**Fodda
 
</pre>
 
 
 
 
 
The ''#tree'' parser-function would still be used to make bullet lists into trees, but would be optional and would provide an interface to convert the transcluded bullet lists into the javascript required in [http://www.destroydrop.com/javascripts/tree/ dTree] (see [[Extension talk:Treeview5.php#Dtree API]]) to generate a tree.
 
 
 
==Persistance==
 
Persistance is auctomatically handled via cookies, see dtree.js, line 36.
 
 
 
 
 
== Dtree API ==
 
See file api.html in the [http://www.destroydrop.com/javascripts/tree/ dTree] directory.
 
 
 
Basically this extension need to provide an interface from a parser function which deals with nested bullet lists to creating the javascript output. At the bullet list stage the php function [http://nz2.php.net/manual/en/function.htmlspecialchars.php htmlspecialchars] on the input to prevent cross site scripting. See [http://www.technicalinfo.net/papers/CSS.html HTML Code Injection and Cross-site scripting] for details.
 
 
 
==Open/Close parser function==
 
Dedicated parser functions should be provided which inserts the open/close functionality of clades on the tree.
 
<pre>
 
<p><a href="javascript: d.openAll();">open all</a> | <a href="javascript: d.closeAll();">close all</a></p>
 
</pre>
 
Note here that the javascript object is a tree called '''d'''. The functionality will not work for any other tree other than that named '''d'''.
 
 
 
==Tree example==
 
This example generates a tree similar to the example01.html provided with  [http://www.destroydrop.com/javascripts/tree/ dTree]
 
 
 
;Wikisyntax
 
<pre>
 
{{#tree:openlevels=4|root='My example tree'|
 
*Node 1
 
**Node 1.1
 
***Node 1.1.1
 
****Node 1.1.1.1
 
*Node 2
 
*Node 3
 
*Node 4
 
*My Pictures
 
**The trip to Iceland
 
**Mom's birthday
 
*Recycle bin
 
}}
 
</pre>
 
 
 
;Original javascript syntax;
 
<pre>
 
d = new dTree('d');
 
 
 
d.add(0,-1,'My example tree');
 
d.add(1,0,'Node 1','/wiki/index.php?title=Sandbox');
 
d.add(2,0,'Node 2','example01.html');
 
d.add(3,1,'Node 1.1','example01.html');
 
d.add(4,0,'Node 3','example01.html');
 
d.add(5,3,'Node 1.1.1','example01.html');
 
d.add(6,5,'Node 1.1.1.1','example01.html');
 
d.add(7,0,'Node 4','example01.html');
 
d.add(8,1,'Node 1.2','example01.html');
 
d.add(9,0,'My Pictures','example01.html','Pictures I\'ve taken over the years','','','img/imgfolder.gif');
 
d.add(10,9,'The trip to Iceland','example01.html','Pictures of Gullfoss and Geysir');
 
d.add(11,9,'Mom\'s birthday','example01.html');
 
d.add(12,0,'Recycle Bin','example01.html','','','img/trash.gif');
 
 
 
document.write(d);
 
</pre>
 
;Equivalent reordered javascript syntax;
 
<pre>
 
e = new dTree('e');
 
 
 
e.add(0,-1,'My example tree');
 
e.add(1,0,'Node 1','/wiki/index.php?title=Sandbox');
 
e.add(2,1,'Node 1.1','example01.html');
 
e.add(3,2,'Node 1.1.1','example01.html');
 
e.add(4,3,'Node 1.1.1.1','example01.html');
 
e.add(5,1,'Node 1.2','example01.html');
 
e.add(6,0,'Node 2','example01.html');
 
e.add(7,0,'Node 3','example01.html');
 
e.add(8,0,'Node 4','example01.html');
 
e.add(9,0,'My Pictures','example01.html','Pictures I\'ve taken over the years','','','img/imgfolder.gif');
 
e.add(10,9,'The trip to Iceland','example01.html','Pictures of Gullfoss and Geysir');
 
e.add(11,9,'Mom\'s birthday','example01.html');
 
e.add(12,0,'Recycle Bin','example01.html','','','img/trash.gif');
 
 
document.write(e);
 
 
 
</pre>
 
The ''add'' method syntax is simplified by the fact that the third argument appears to allow html code, so links etc can be created from html generated from the MediaWiki parser.
 
 
 
The current treeview5.php can be configured to use images from dtree during development.
 
{{code|<PHP>
 
include("$IP/extensions/Treeview/Treeview5.php");
 
# Approximated by
 
$wgTreeViewImages['plus']  = "Nolines plus.gif";
 
$wgTreeViewImages['minus']  = "Nolines minus.gif";
 
$wgTreeViewImages['opened'] = "Folderopen.gif";
 
$wgTreeViewImages['closed'] = "Folder.gif";
 
$wgTreeViewImages['doc']    = "Page.gif";
 
$wgTreeViewImages['spacer'] = "Empty.gif";
 
  
# These images are needed if you have $wgTreeViewShowLines set
+
But after thinking about the problem more closely, it turns out that the tree can be constructed without depth information by converting each tree row into a new format containing depth and tree id information which is protected from wiki-parsing. This information can then be converted into tree JavaScript after the parser has finished in the ''ParserAfterTidy'' hook.
$wgTreeViewImages['vert'] = 'Line.gif';
 
$wgTreeViewImages['node'] = 'Join.gif';
 
$wgTreeViewImages['last'] = 'Joinbottom.gif';
 
</PHP>
 
}}
 
==Defining the location dtree images==
 
This is not a good way to link to the images however for the purposes of documenting noticed effects, images locations can be defined in the dtree.js file. A browsers cache may need to be updated to link to the images.
 
{{code|<PHP>
 
this.icon = {
 
root : 'extensions/treeview/img/base.gif',
 
folder : 'extensions/treeview/img/folder.gif',
 
folderOpen : 'extensions/treeview/img/folderopen.gif',
 
node : 'extensions/treeview/img/page.gif',
 
empty : 'extensions/treeview/img/empty.gif',
 
line : 'extensions/treeview/img/line.gif',
 
join : 'extensions/treeview/img/join.gif',
 
joinBottom : 'extensions/treeview/img/joinbottom.gif',
 
plus : 'extensions/treeview/img/plus.gif',
 
plusBottom : 'extensions/treeview/img/plusbottom.gif',
 
minus : 'extensions/treeview/img/minus.gif',
 
minusBottom : 'extensions/treeview/img/minusbottom.gif',
 
nlPlus : 'extensions/treeview/img/nolines_plus.gif',
 
nlMinus : 'extensions/treeview/img/nolines_minus.gif'
 
        }
 
</PHP>
 
}}
 
  
==Defining the root of the tree==
+
Here is the wikitext of an example tree which includes two transcluded tree's
The root of the tree could be optionally specified with a parameter called ''root'' in the parser function options. It must be specified as either;
 
 
 
'''unnamed'''
 
 
<pre>
 
<pre>
e.add(0,-1,'');
+
*[[toplevl]]
 +
**bar
 +
***baz
 +
*foo
 +
**[[Bar]]
 +
***{{:Foo}}
 +
**[[Baz]]
 +
***{{:Bar}}
 
</pre>
 
</pre>
or '''named'''
 
<pre>
 
e.add(0,-1,'My example tree');
 
</pre>
 
for the tree to be rendered.
 
 
{{code|<php>
 
<?php
 
 
$in = "*Node 1
 
**Node 1.1
 
***Node 1.1.1
 
****Node 1.1.1.1
 
*Node 2
 
*Node 3
 
*Node 4
 
*My Pictures
 
**The trip to Iceland
 
**Mom's birthday
 
*Recycle bin";
 
 
$a = new treeview();
 
 
$a->makeDTree($in);
 
 
class treeview {
 
 
  var $wikitext;
 
 
  function makeDTree(&$tmp) {
 
    $lines = split("\n", $tmp);
 
 
    foreach($lines as $line) {
 
      $count = 0;
 
      /**
 
      * Note preg_replace expects the bullets to be at the start of the line
 
      **/
 
      $node = preg_replace("/\\*/", "", $line, -1, $count);
 
      print "$count<br />";
 
      print "OUTPUT=>$node<br />";
 
     
 
    }
 
 
   
 
    function addDTree($id,$pid,$name,$url,$title,$target, $icon,$iconOpe,$open) {
 
      return("mytree.add($id,$pid,$name,$url,$title,$target, $icon,$iconOpe,$open);");
 
     
 
    }
 
  
  }
+
The ''expandTree'' method (the ''#tree'' parser-function hook) then converts all the matched rows into the following general structure:
 +
1{uniq}-{id}-{depth}-{item}2{uniq}
 +
Where ''uniq'' is a unique string representing treeview's in general, ''id'' is a unique string representing the current tree, ''depth'' is the depth of the current row with respect to the current tree root, and ''item'' is the wikitext within the current row. The information is "protected" from the parser merely by the fact that it contains no markup characters, but this could be made more robust later.
  
}
+
After the resulting structure has been parsed, the content portions will have been converted into HTML and the information portions remain unchanged. Below is the HTML source of the above example after parsing and printed at the start of the ''renderTree'' method (the ''ParserAfterTidy'' hook).
</php>
 
}}
 
 
 
== How it works ==
 
There are basically two stages to the way the treeview is working the first stage is the parser-function (the ''Tree'' function) which is designed to allow bullet lists to work with proper depth when transcluded. The result of the parser-function is a single bullet list which is surrounded in &lt;treeviewxxx> tags to be converted into the proper tree HTML. It was very difficult to get the transclusion to work properly and required some [[w:Voodoo programming|voodoo]] programming.
 
 
 
=== Explanation of voodoo ===
 
The voodoo is required because the wiki parser works from deepest first to top level last, and protects tags inside GUID's which are not exposed to the parser which effectively puts them off-limits to any possible adjustment. The voodoo finds these protected GUID's and replaces them with normal bullet content which is not protected from further parsing and so becomes part of the higher level's content.
 
 
 
=== Explanation of guids ===
 
After the first stage of wiki-parsing (i.e. the processing of curly braces and protection of tag content) each tree will be a single wikitext bullet list to be processed by the tag hook (the ''treeview'' function). This function is essentially a two-stage process.
 
 
 
The first stage of this function converts the wikitext bullet list into list of items matchable by unique id's eg,
 
<pre>
 
*[[foo]]
 
**[[bar]]
 
</pre>
 
would become something like this:
 
 
<pre>
 
<pre>
UNIQxxxx1-<a href=.....>foo</a>UNIQxxxx
+
1tv47c8d2444a04d-47c8d2447a3e7-1-<a href="/wiki/index.php?title=Toplevl&amp;action=edit" class="new" title="Toplevl">toplevl</a>2tv47c8d2444a04d
UNIQxxxx2-<a href=.....>bar</a>UNIQxxxx
+
1tv47c8d2444a04d-47c8d2447a3e7-2-bar2tv47c8d2444a04d
 +
1tv47c8d2444a04d-47c8d2447a3e7-3-baz2tv47c8d2444a04d
 +
1tv47c8d2444a04d-47c8d2447a3e7-1-foo2tv47c8d2444a04d
 +
1tv47c8d2444a04d-47c8d2447a3e7-2-<a href="/Bar" title="Bar">Bar</a>2tv47c8d2444a04d
 +
1tv47c8d2444a04d-47c8d2447a3e7-3-1tv47c8d2444a04d-47c8d244781d7-1-<a href="/Foo" title="Foo">Foo</a>2tv47c8d2444a04d2tv47c8d2444a04d
 +
1tv47c8d2444a04d-47c8d244781d7-2-bar2tv47c8d2444a04d
 +
1tv47c8d2444a04d-47c8d244781d7-3-baz2tv47c8d2444a04d
 +
1tv47c8d2444a04d-47c8d244781d7-1-foo2tv47c8d2444a04d
 +
1tv47c8d2444a04d-47c8d244781d7-2-<a href="/Bar" title="Bar">Bar</a>2tv47c8d2444a04d
 +
1tv47c8d2444a04d-47c8d244781d7-2-<a href="/wiki/index.php?title=Baz&amp;action=edit" class="new" title="Baz">Baz</a>2tv47c8d2444a04d
 +
1tv47c8d2444a04d-47c8d2447a3e7-2-<a href="/wiki/index.php?title=Baz&amp;action=edit" class="new" title="Baz">Baz</a>2tv47c8d2444a04d
 +
1tv47c8d2444a04d-47c8d2447a3e7-3-1tv47c8d2444a04d-47c8d2447a020-1-<a href="/Bar" title="Bar">bar</a>2tv47c8d2444a04d2tv47c8d2444a04d
 +
1tv47c8d2444a04d-47c8d2447a020-2-baz2tv47c8d2444a04d
 +
1tv47c8d2444a04d-47c8d2447a020-3-biz2tv47c8d2444a04d
 
</pre>
 
</pre>
The reason for this is that we need to protect the asterisks from being parsed otherwise they will be converted into UL's and LI's, but the rest of the content (i.e. the wikitext content in each bullet item) should be wiki-parsed as usual. We convert each row into a GUID-based structure so that the rows can be easily matched in the second stage without any possibility of any other content in the page or in a another tree being accidentally matched as well.
 
  
After this conversion all the rows of the current tree can be matched with a single ''preg_match_all''. Each row contains the depth info and the parsed wikitext of the row (and also an icon if the first item of content in a row was an image link). The code which does this conversion is as follows:
+
The following match and loop will then loop through all the rows no matter what level and extract the values back out
 
{{code|<php>
 
{{code|<php>
$text = preg_replace("/(?<=\\*)\\s*\\[\\[Image:(.+?)\\]\\]/","{$uid}3$1{$uid}4",$text);
+
if (preg_match_all("/1$uniq-(.+?)-([0-9]+)-({$uniq}3(.+?){$uniq}4)?(.*?)(?=[12]$uniq)/",$text,$matches,PREG_SET_ORDER)) {
$text = preg_replace_callback("/^(\\*+)(.*?)$/m",array($this,'protectTree'),$text);
+
foreach ($matches as $n => $row) {
$out  = $parser->parse($text,$parser->mTitle,$parser->mOptions,false,false);
+
list(,$id,$depth,,$icon,$item) = $row;
$text = $out->getText();
 
 
 
function protectTree($m) { return "1{$this->uid}".strlen($m[1])."-$m[2]{$this->uid}2\n"; }
 
 
</php>}}
 
</php>}}
  
The ''preg_match_all'' expression and way to loop through the resulting matched rows and obtain their info to build the HTML or JS of a tree is as follows:
+
Here is a bullet list of the ''id'', ''depth'' and ''item'' information from the rows when running the above code over the HTML source shown above:
{{code|<php>
+
* 47c8d0860fe80:1,toplevl
if (preg_match_all("/1{$uid}([0-9]+)-({$uid}3(.+?){$uid}4)?(.+?){$uid}2/",$text,$matches,PREG_SET_ORDER)) {
+
* 47c8d0860fe80:2,bar
foreach ($matches as $row) {
+
* 47c8d0860fe80:3,baz
list(,$depth,,$icon,$html) = $row;
+
* 47c8d0860fe80:1,foo
...
+
* 47c8d0860fe80:2,Bar
</php>}}
+
* 47c8d0860fe80:3,
 +
* 47c8d08609a27:1,Foo
 +
* 47c8d08609a27:2,bar
 +
* 47c8d08609a27:3,baz
 +
* 47c8d08609a27:1,foo
 +
* 47c8d08609a27:2,Bar
 +
* 47c8d08609a27:2,Baz
 +
* 47c8d0860fe80:2,Baz
 +
* 47c8d0860fe80:3,
 +
* 47c8d0860fa81:1,bar
 +
* 47c8d0860fa81:2,baz
 +
* 47c8d0860fa81:3,biz
  
==Bugs==
+
The content of the loop then converts the list into JavaScript statements which result in a ''dTree''. The correct recursive depths are calculated by adding the last depth to the current if its ''id'' is a new one.
*A tree with an unnamed external url fails e.g. http://www.google.com
 
  
 
== See also ==
 
== See also ==
 
*[http://www.destroydrop.com/javascripts/tree/ dTree] (javascript based)
 
*[http://www.destroydrop.com/javascripts/tree/ dTree] (javascript based)
 
*[http://www.silpstream.com/blog/wp-dtree/  Slipstream wp-Dtree] (php based)
 
*[http://www.silpstream.com/blog/wp-dtree/  Slipstream wp-Dtree] (php based)

Revision as of 04:09, 1 March 2008

Info.svg This talk page pertains specifically to the development of this extension. For more general discussion about bugs and usage etc, please refer to the mediawiki.org talk page at MW:Extension talk:Tree view

Treeview5.1 is a new approach to the core recursive bullet-list code which doesn't require any voodoo, and should be compatible with all MediaWiki versions including 1.12.

How it works

The initial problem was how to write a parser-function which could generate a tree which could be composed of other transcluded tree's (i.e. parser-functions which are recursively executed forming a larger whole structure together). This seemed impossible since there are no MediaWiki properties available in the environment which reveal the current depth that the parser-function expansion is operating at.

But after thinking about the problem more closely, it turns out that the tree can be constructed without depth information by converting each tree row into a new format containing depth and tree id information which is protected from wiki-parsing. This information can then be converted into tree JavaScript after the parser has finished in the ParserAfterTidy hook.

Here is the wikitext of an example tree which includes two transcluded tree's

*[[toplevl]]
**bar
***baz
*foo
**[[Bar]]
***{{:Foo}}
**[[Baz]]
***{{:Bar}}

The expandTree method (the #tree parser-function hook) then converts all the matched rows into the following general structure:

1{uniq}-{id}-{depth}-{item}2{uniq}

Where uniq is a unique string representing treeview's in general, id is a unique string representing the current tree, depth is the depth of the current row with respect to the current tree root, and item is the wikitext within the current row. The information is "protected" from the parser merely by the fact that it contains no markup characters, but this could be made more robust later.

After the resulting structure has been parsed, the content portions will have been converted into HTML and the information portions remain unchanged. Below is the HTML source of the above example after parsing and printed at the start of the renderTree method (the ParserAfterTidy hook).

1tv47c8d2444a04d-47c8d2447a3e7-1-<a href="/wiki/index.php?title=Toplevl&action=edit" class="new" title="Toplevl">toplevl</a>2tv47c8d2444a04d
1tv47c8d2444a04d-47c8d2447a3e7-2-bar2tv47c8d2444a04d
1tv47c8d2444a04d-47c8d2447a3e7-3-baz2tv47c8d2444a04d
1tv47c8d2444a04d-47c8d2447a3e7-1-foo2tv47c8d2444a04d
1tv47c8d2444a04d-47c8d2447a3e7-2-<a href="/Bar" title="Bar">Bar</a>2tv47c8d2444a04d
1tv47c8d2444a04d-47c8d2447a3e7-3-1tv47c8d2444a04d-47c8d244781d7-1-<a href="/Foo" title="Foo">Foo</a>2tv47c8d2444a04d2tv47c8d2444a04d
1tv47c8d2444a04d-47c8d244781d7-2-bar2tv47c8d2444a04d
1tv47c8d2444a04d-47c8d244781d7-3-baz2tv47c8d2444a04d
1tv47c8d2444a04d-47c8d244781d7-1-foo2tv47c8d2444a04d
1tv47c8d2444a04d-47c8d244781d7-2-<a href="/Bar" title="Bar">Bar</a>2tv47c8d2444a04d
1tv47c8d2444a04d-47c8d244781d7-2-<a href="/wiki/index.php?title=Baz&action=edit" class="new" title="Baz">Baz</a>2tv47c8d2444a04d
1tv47c8d2444a04d-47c8d2447a3e7-2-<a href="/wiki/index.php?title=Baz&action=edit" class="new" title="Baz">Baz</a>2tv47c8d2444a04d
1tv47c8d2444a04d-47c8d2447a3e7-3-1tv47c8d2444a04d-47c8d2447a020-1-<a href="/Bar" title="Bar">bar</a>2tv47c8d2444a04d2tv47c8d2444a04d
1tv47c8d2444a04d-47c8d2447a020-2-baz2tv47c8d2444a04d
1tv47c8d2444a04d-47c8d2447a020-3-biz2tv47c8d2444a04d

The following match and loop will then loop through all the rows no matter what level and extract the values back out

{{{1}}}

Here is a bullet list of the id, depth and item information from the rows when running the above code over the HTML source shown above:

  • 47c8d0860fe80:1,toplevl
  • 47c8d0860fe80:2,bar
  • 47c8d0860fe80:3,baz
  • 47c8d0860fe80:1,foo
  • 47c8d0860fe80:2,Bar
  • 47c8d0860fe80:3,
  • 47c8d08609a27:1,Foo
  • 47c8d08609a27:2,bar
  • 47c8d08609a27:3,baz
  • 47c8d08609a27:1,foo
  • 47c8d08609a27:2,Bar
  • 47c8d08609a27:2,Baz
  • 47c8d0860fe80:2,Baz
  • 47c8d0860fe80:3,
  • 47c8d0860fa81:1,bar
  • 47c8d0860fa81:2,baz
  • 47c8d0860fa81:3,biz

The content of the loop then converts the list into JavaScript statements which result in a dTree. The correct recursive depths are calculated by adding the last depth to the current if its id is a new one.

See also