\n";
}
Markup('`markup','links',"/`\./",''); ## included in extendmarkup.php
## page table of contents
$IdPattern = "[A-Za-z][-.:\w]*";
if ($format=='pdf') {
SDV($DefaultTocTitle,'Contents');
SDV($TocHeaderFmt,
'[[#toc]]$TocTitle');
SDV($RemoteTocFmt,
'Contents of [[$Toc(#toc)]]');
} else {
SDV($DefaultTocTitle,'On this page...');
SDV($TocHeaderFmt,'[[#toc]]$TocTitle');
SDV($RemoteTocFmt,'On page [[$Toc(#toc)]]...');
}
SDV($NumberToc,true);
SDV($L1TocChar, '.');
SDV($OmitQMarkup,false);
SDV($MaxTocDepth,2);
if ($action=="print" || $action=="publish") {
Markup('[[##','<[[#','/\[\[##([A-Za-z][-.:\w]*)\]\]/','[[#$1]]');
if ($action=='publish') Markup('toc','>include',
'/\(:([#\*])?toc(?:-(float|hide))?(?:\s+anchors=(v)isible)?(?:\s+(.*?))?:\)/', '');
Markup('tocback','directives','/\(:toc-back(?:\s+(.*?))?:\)/','');
} else {
Markup('[[##','<[[#','/\[\[##([A-Za-z][-.:\w]*)\]\]/e',
"Keep(\"$VisibleAnchor\",
'L')");
}
Markup('toc','>nl1',
'/\(:([#\*])?toc(?:-(float|hide))?(?:\s+anchors=(v)isible)?(?:\s+(.*?))?(?:\s+(Q))?:\)(.*)$/se',
"TableOfContents(\$pagename,'$1','$2',PSS('$4'),'$5',PSS('$6')).
TocEntryAnchors('$3',PSS('$6'))");
SDV($TocBackFmt,'↑ Contents');
Markup('tocback','directives','/\(:toc-back(?:\s+(.*?))?:\)/e',
"'[[#toc | '.TocLinkText(PSS('$1')).']]'");
Markup('tocpage','directives','/\(:toc-page\s+(.*?)(?:\s+self=([01]))?:\)/e',
"RemoteTableOfContents(\$pagename,'$1','$2')");
function RemoteTableOfContents($pagename,$ref,$self=0) {
global $TocHeaderFmt,$RemoteTocFmt;
$oTocHeader = $TocHeaderFmt;
$TocHeaderFmt = str_replace('$Toc',$ref,$RemoteTocFmt);
$tocname = MakePageName($pagename,$ref);
if ($tocname==$pagename && $self==0) return '';
$tocpage=RetrieveAuthPage($tocname,'read',false);
$toctext=@$tocpage['text'];
if (preg_match('/\(:([#\*])?toc(?:-(float|hide))?(?:\s+anchors=(v)isible)?(?:\s+(.*?))?(?:\s+(Q))?:\)(.*)$/se',$toctext,$m))
$toc = str_replace('[[#',"[[$ref#",
TableOfContents($tocname,$m[1],'page','',$m[5],PSS($m[6])));
$TocHeaderFmt = $oTocHeader;
return $toc;
}
function TocLinkText($text) {
global $TocBackFmt;
if ($text) $TocBackFmt = $text;
return $TocBackFmt;
}
function TocEntryAnchors($visible,$text) {
global $IdPattern;
global $NumberToc;
$outline = CreateOutline($text);
$indextree = $outline[0];
$records = $outline[1];
foreach ($records as $record) {
// chapter number, heading text, heading level, real level, wiki reference, matched expr, anchor id
$chapternum = $record[0];
$headingtext = $record[1];
$level = $record[3];
$matchedexpr = str_replace("\n", "", $record[5]);
$levelstr = str_pad("", $level, "!");
if ($NumberToc) {
$replacement = sprintf("%s#%s %s", $levelstr, $chapternum, $headingtext);
} else {
$replacement = sprintf("%s%s", $levelstr, $headingtext);
}
$text = str_replace($matchedexpr, $replacement, $text);
}
return $text;
}
/**
* Explode any single-dimensional array into a full blown tree structure,
* based on the delimiters found in it's keys.
*
* @author Kevin van Zonneveld
* @author Lachlan Donald
* @author Takkie
* @copyright 2008 Kevin van Zonneveld (http://kevin.vanzonneveld.net)
* @license http://www.opensource.org/licenses/bsd-license.php New BSD Licence
* @version SVN: Release: $Id: explodeTree.inc.php 89 2008-09-05 20:52:48Z kevin $
* @link http://kevin.vanzonneveld.net/
*
* @param array $array
* @param string $delimiter
* @param boolean $baseval
*
* @return array
*/
function explodeTree($array, $delimiter = '_', $baseval = false)
{
if(!is_array($array)) return false;
$splitRE = '/' . preg_quote($delimiter, '/') . '/';
$returnArr = array();
foreach ($array as $key => $val) {
// Get parent parts and the current leaf
$parts = preg_split($splitRE, $key, -1, PREG_SPLIT_NO_EMPTY);
$leafPart = array_pop($parts);
// Build parent structure
// Might be slow for really deep and large structures
$parentArr = &$returnArr;
foreach ($parts as $part) {
if (!isset($parentArr[$part])) {
$parentArr[$part] = array();
} elseif (!is_array($parentArr[$part])) {
if ($baseval) {
$parentArr[$part] = array('__base_val' => $parentArr[$part]);
} else {
$parentArr[$part] = array();
}
}
$parentArr = &$parentArr[$part];
}
// Add the final part to the structure
if (empty($parentArr[$leafPart])) {
$parentArr[$leafPart] = $val;
} elseif ($baseval && is_array($parentArr[$leafPart])) {
$parentArr[$leafPart]['__base_val'] = $val;
}
}
return $returnArr;
}
/**
* Calculates the chapter numbers by the location of the TOC entry within the
* tree. This means that it doesn't necessarily reflect the heading information.
* To give a short example:
*
* !A
* !!!!!B
* !!C
*
* This would cause the following chapter numbers:
*
* 1. A
* 1.1. B
* 1.2. C
*
* As you can see B and C are on the same level here. We can't "guess" a parent
* for C here as there's no second level element. Nevertheless this is a really
* special case which simply is bad style by the wiki writer.
*
* @param $arr The tree which contains the outline with the indices.
* @param $values The list with all records.
* @param $prefix A prefix used to easily calculate the chapter numbers.
* @param $level The current level.
*/
function CalculateChapters( $arr, &$values, $prefix = "", $level = 1 ) {
global $L1TocChar;
$count = 1;
foreach($arr as $k=>$v){
if (strlen($prefix) == 0) {
$chapter = sprintf("%d", $count);
} else {
$chapter = sprintf("%s%s%d", $prefix, $L1TocChar, $count);
}
// skip the baseval thingy. Not a real node.
if($k == "__base_val") {
continue;
}
$index = ( is_array($v) ? $v["__base_val"] : $v );
$record = &$values[$index];
$record[0] = $chapter;
$record[3] = $level;
if(is_array($v)){
// this is what makes it recursive, rerun for childs
CalculateChapters($v, $values, $chapter, $level + 1);
}
$count++;
}
}
/**
* Stupid helper function which combines the both path segments.
*
* @todo [01-Jun-2011:KASI] I suspect that there's a better way to do this concatenation (see 'reduce' below)
*/
function jjoin($a, $b) {
if (strlen($a) == 0) {
return $b;
} elseif (strlen($b) == 0) {
return $a;
} else {
return $a . "/" . $b;
}
}
/**
* This function basically creates the outline. The outline is a tree structure where each node
* contains an index to a list. The index points to a record with the corresponding TOC entry
* data (this data isn't stored directly within the tree as using arrays as node values would
* make the iteration somewhat more complicated since an array is used to identify a parental
* node).
*
* Anyway this function works in 3 steps:
*
* 1. Create an array which maps (path -> index). The list will be filled with values
* accordingly.
* 2. Translated this array into a tree structure.
* 3. Run a postprocess which writes the "chapter" numbers to the records.
*
* @param $text The input text which needs to be processed.
*
* @return A pair which consists of the tree and the list with the reords.
*/
function CreateOutline( $text ) {
global $IdPattern,$DefaultTocAnchor,$OmitQMarkup;
preg_match_all( "/\n(!+|Q?:)\s*(\[\[#+$IdPattern\]\]|#*)([^\n]*)/", $text, $match );
$counter = 0;
$pathlist = array();
$list = array();
$stack = array();
$lastlevel = 0;
for( $i = 0; $i < count( $match[0] ); $i++ ) {
if( $match[1][$i]==':' || ( $match[1][$i] == 'Q:' && $OmitQMarkup ) ) {
if( $match[2][$i] && $match[2][$i][0]=='#' ) {
$counter++;
}
continue;
}
$idpattern = preg_replace( "/^(\\[\\[#)#/", "$1", trim( $match[2][$i] ) );
$idpattern = preg_replace( "/^#+/",'', $idpattern );
$t = preg_replace( '/%(center|right)%/','', $match[3][$i] );
// get the urrent level
$level = strlen( $match[1][$i] );
if( $level <= $lastlevel ) {
// we've moved to a higher level, so drop all irrelevant
// path segments from our stack
$count = $lastlevel - $level + 1;
while( $count > 0 ) {
array_pop( $stack );
$count--;
}
} else if( $level > ($lastlevel + 1) ) {
// we've moved to a lower level, so we might need to push
// some dummy elements (usually this won't happen if the
// user hasn't written crap-like wiki text.
$count = $level - $lastlevel - 2;
while( $count > 0 ) {
array_push( $stack, "" );
$count--;
}
}
$lastlevel = $level;
// push the current heading text as a path segment onto the stack
array_push( $stack, $t );
// calculate the anchorid and the wiki reference
$id = "";
if( strpos($idpattern,'[#') == 1 ) {
$id = str_replace( '[', '', str_replace( ']]', '', $idpattern ) );
$ref = str_replace( ']]', ' | ' . CrossReference( $pagename, "$idpattern$t", preg_replace( "/\[\[#(.*?)\]\]/","$1",$idpattern)) . ']]', $idpattern );
} else {
$counter++;
$id = "#$DefaultTocAnchor$counter";
$ref = "[[$id | " . CrossReference( $pagename, "[[#]]$t", "" ) . ']]';
}
// create a treepath from the current stack state
$treepath = array_reduce($stack, "jjoin", "");
// get the index for the treenode and the list which contains the data
$index = count( $pathlist );
// chapter number, heading text, heading level, real leve, wiki reference, matched expr, anchor id
$list [ $index ] = array( "", $t, $level, 0, $ref, $match[0][$i], $id );
$pathlist[ $treepath ] = $index;
}
// now transform the simple list of (path->index) into an apropriate tree which
// will represent the outline
$indextree = explodeTree( $pathlist, '/', true );
// calculate chapter numbers and save them
CalculateChapters( $indextree, $list );
// return the outline information
return array( $indextree, $list );
}
/**
* This function generates the TOC based upon the supplied outline data.
*
* @param $tree The tree structure representing the outline. The nodes only contain indices to the records list.
* @param $records The list with the necessary outline information.
* @param $listitemelement The tag for list elements.
* @param $listopenelement The opener for list elements.
* @param $listcloseelement The closer for list elements.
* @param $level The current level of the TOC.
*
* @return The chained TOC lists.
*/
function GenerateToc( $tree, $records, $listitemelement, $listopenelement, $listcloseelement, $level = 0 ) {
global $MaxTocDepth, $NumberToc;
// check whether the user specified a maximum depth, so we won't iterate any deeper level
if( $MaxTocDepth > 0 ) {
if( $level > $MaxTocDepth ) {
return "";
}
}
$result = sprintf( "<%s>", $listopenelement );
foreach( $tree as $k=>$v ){
// skip the baseval thingy. Not a real node.
if( $k == "__base_val" ) {
continue;
}
// get the index which is stored within the tree
$index = ( is_array($v) ? $v["__base_val"] : $v );
// get the record data
$record = $records[$index];
$chapter = $record[0]; // chapter number
$text = $record[1]; // heading text
$heading = $record[2]; // heading level (f.e. !! = 2)
$reallevel = $record[3]; // heading level (f.e. !! = 2); fixed in case the levels within the text was invalid
$reference = $record[4]; // wiki reference (f.e. [[#bibo | Bibo]])
$complete = $record[5]; // the completely matched expression within the source
$anchorid = $record[6]; // the anchor ID (f.e. bibo)
$sublist = "";
$numbering = "";
if( $NumberToc ) {
$numbering = $chapter . " ";
}
if( is_array($v) ) {
// we've got child elements, so generate a contained list here
$sublist = "\n".GenerateToc($v, $records, $listitemelement, $listopenelement, $listcloseelement, $level + 1);
}
// create the link for this current TOC entry
$result .= sprintf("<%s>%s%s%s%s>\n", $listitemelement, $numbering, $reference, $sublist, $listitemelement);
}
$result .= sprintf( "%s>", $listcloseelement );
return $result;
}
/**
* This function is responsible for the creation of the TOC which is basically a chained list.
*
* @param $pagename The name of the page which has to be used as the input for the TOC.
* @param $number Either '*' or '#' in case the $NumberToc parameter shall be overridden.
* @param $float Render the TOC in a floated environment.
* @param $title The title to be used for the head of the TOC.
* @param $includeq Support Q Markup (???)
* @param $text The input text of the page.
*
* @return The chained TOC lists.
*/
function TableOfContents($pagename,$number,$float,$title,$includeq,$text) {
global $DefaultTocTitle,$TocHeaderFmt,$IdPattern,$NumberToc,$OmitQMarkup,
$format,$L1TocChar,$DefaultTocAnchor,$TocFloat,$TocToggle,$HTMLHeaderFmt,
$ToggleText;
if( $includeq ) {
$OmitQMarkup = (!$OmitQMarkup);
}
if( $float == 'float' ) {
$TocFloat = (!$TocFloat);
}
if( ! $title ) {
$title = $DefaultTocTitle;
}
$toc = str_replace('$TocTitle',$title,$TocHeaderFmt);
if( $number=='*' ) {
$NumberToc = false;
} elseif( $number=='#' ) {
$NumberToc = true;
}
// make some preparations for the elements that will be used for format specific output
if( $format == 'pdf' ) {
$l = 'tbook:item';
$s = ($NumberToc) ? 'tbook:enumerate' : 'tbook:itemize';
$sc = $s;
$toc = "$toc"."<$sc><$l>\$List$l>$s>";
} elseif( $float=='hide' ) {
return '';
} else {
$tocid = ($float=='page') ? 'ptocid' : 'tocid'; // remote toc?
$toggle = " ({$ToggleText[0]})";
$l = 'li';
$s = ($NumberToc) ? 'ol' : 'ul';
$sc = "$s class='toc'";
$f = ($TocFloat) ? 'float' : '';
if( $TocToggle ) {
$toc = "
$toc$toggle
" . "<$sc id='$tocid'><$l>\$List$l>$s>
";
} else {
$toc = "
<$sc id='$tocid'><$l>\$List$l>$s>
";
}
}
// calculate the outline in order to generate the toc
$outline = CreateOutline($text);
$indextree = $outline[0];
$records = $outline[1];
$r = GenerateToc($indextree, $records, $l, $sc, $s);
if ($r!='') {
// insert the toc
$r = str_replace('$List',$r,$toc);
}
return $r;
}
?>