1<?php 2 3require_once DOKU_PLUGIN . 'odt/ODT/ODTDocument.php'; 4 5/** 6 * ODTIndex: 7 * Class containing static code for handling indexes. 8 * Actually these are the table of contents and the chapter index. 9 * 10 * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) 11 */ 12class ODTIndex 13{ 14 /** 15 * This function does not really render/insert an index but inserts a placeholder. 16 * See also replaceIndexesPlaceholders(). 17 * 18 * @return string 19 */ 20 public static function insertIndex(ODTInternalParams $params, array &$indexesData, $type='toc', array $settings=NULL) { 21 // Insert placeholder 22 $index_count = count ($indexesData); 23 24 $params->document->paragraphClose(); 25 $params->content .= '<index-placeholder no="'.($index_count+1).'"/>'; 26 27 // Prepare index data 28 $new = array(); 29 foreach ($settings as $key => $value) { 30 $new [$key] = $value; 31 } 32 $new ['type'] = $type; 33 $new ['width'] = $params->document->getAbsWidthMindMargins(); 34 35 if ($type == 'chapter') { 36 $new ['start_ref'] = $params->document->getPreviousToCItem(1); 37 } else { 38 $new ['start_ref'] = NULL; 39 } 40 41 // Add new index data 42 $indexesData [] = $new; 43 44 return ''; 45 } 46 47 /** 48 * This function builds the actual TOC and replaces the placeholder with it. 49 * It is called in document_end() after all headings have been added to the TOC, see toc_additem(). 50 * The page numbers are just a counter. Update the TOC e.g. in LibreOffice to get the real page numbers! 51 * 52 * The TOC is inserted by the syntax tag '{{odt>toc:setting=value;}};'. 53 * The following settings are supported: 54 * - Title e.g. '{{odt>toc:title=Example;}}'. 55 * Default is 'Table of Contents' (for english, see language files for other languages default value). 56 * - Leader sign, e.g. '{{odt>toc:leader-sign=.;}}'. 57 * Default is '.'. 58 * - Indents (in cm), e.g. '{{odt>toc:indents=indents=0,0.5,1,1.5,2,2.5,3;}};'. 59 * Default is 0.5 cm indent more per level. 60 * - Maximum outline/TOC level, e.g. '{{odt>toc:maxtoclevel=5;}}'. 61 * Default is taken from DokuWiki config setting 'maxtoclevel'. 62 * - Insert pagebreak after TOC, e.g. '{{odt>toc:pagebreak=1;}}'. 63 * Default is '1', means insert pagebreak after TOC. 64 * - Set style per outline/TOC level, e.g. '{{odt>toc:styleL2="color:red;font-weight:900;";}}'. 65 * Default is 'color:black'. 66 * 67 * It is allowed to use defaults for all settings by using '{{odt>toc}}'. 68 * Multiple settings can be combined, e.g. '{{odt>toc:leader-sign=.;indents=0,0.5,1,1.5,2,2.5,3;}}'. 69 */ 70 public static function replaceIndexesPlaceholders(ODTInternalParams $params, array $indexesData, array $toc) { 71 $index_count = count($indexesData); 72 for ($index_no = 0 ; $index_no < $index_count ; $index_no++) { 73 $data = $indexesData [$index_no]; 74 75 // At the moment it does not make sense to disable links for the TOC 76 // because LibreOffice will insert links on updating the TOC. 77 $data ['create_links'] = true; 78 $indexContent = self::buildIndex($params->document, $toc, $data, $index_no+1); 79 80 // Replace placeholder with TOC content. 81 $params->content = str_replace ('<index-placeholder no="'.($index_no+1).'"/>', $indexContent, $params->content); 82 } 83 } 84 85 /** 86 * This function builds a TOC or chapter index. 87 * The page numbers are just a counter. Update the TOC e.g. in LibreOffice to get the real page numbers! 88 * 89 * The layout settings are taken from the configuration and $settings. 90 * $settings can include the following options syntax: 91 * - Title e.g. 'title=Example;'. 92 * Default is 'Table of Contents' (for english, see language files for other languages default value). 93 * - Leader sign, e.g. 'leader-sign=.;'. 94 * Default is '.'. 95 * - Indents (in cm), e.g. 'indents=indents=0,0.5,1,1.5,2,2.5,3;'. 96 * Default is 0.5 cm indent more per level. 97 * - Maximum outline/TOC level, e.g. 'maxtoclevel=5;'. 98 * Default is taken from DokuWiki config setting 'maxtoclevel'. 99 * - Insert pagebreak after TOC, e.g. 'pagebreak=1;'. 100 * Default is '1', means insert pagebreak after TOC. 101 * - Set style per outline/TOC level, e.g. 'styleL2="color:red;font-weight:900;";'. 102 * Default is 'color:black'. 103 * 104 * It is allowed to use defaults for all settings by omitting $settings. 105 * Multiple settings can be combined, e.g. 'leader-sign=.;indents=0,0.5,1,1.5,2,2.5,3;'. 106 */ 107 protected static function buildIndex(ODTDocument $doc, array $toc, array $settings, $indexNo) { 108 $stylesL = array(); 109 $stylesLNames = array(); 110 111 // Get index type 112 $type = $settings ['type']; 113 114 // It seems to be not supported in ODT to have a different start 115 // outline level than 1. 116 $max_outline_level = 10; 117 if (!empty($settings ['maxlevel'])) { 118 $max_outline_level = $settings ['maxlevel']; 119 } 120 121 // Determine title, default for table of contents is 'Table of Contents'. 122 // Default for chapter index is empty. 123 // Syntax for 'Test' as title would be "title=test;". 124 $title = ''; 125 if (!empty($settings ['title'])) { 126 $title = $settings ['title']; 127 } 128 129 // Determine leader-sign, default is '.'. 130 // Syntax for '.' as leader-sign would be "leader_sign=.;". 131 $leader_sign = '.'; 132 if (!empty($settings ['leader_sign'])) { 133 $leader_sign = $settings ['leader_sign']; 134 } 135 136 // Determine indents, default is '0.5' (cm) per level. 137 // Syntax for a indent of '0.5' for 5 levels would be "indents=0,0.5,1,1.5,2;". 138 // The values are absolute for each level, not relative to the higher level. 139 $indents = '0, 0.5, 1, 1.5, 2, 2.5, 3, 3.5, 4, 4.5'; 140 if (!empty($settings ['indents'])) { 141 $indents = $settings ['indents']; 142 } 143 144 // Determine pagebreak, default is on '1'. 145 // Syntax for pagebreak off would be "pagebreak=0;". 146 $pagebreak = true; 147 if (!empty($settings ['pagebreak'])) { 148 $temp = $settings ['pagebreak']; 149 $pagebreak = false; 150 if ( $temp == '1' ) { 151 $pagebreak = true; 152 } else if ( strcasecmp($temp, 'true') == 0 ) { 153 $pagebreak = true; 154 } 155 } 156 157 // Determine text style for the index heading. 158 $styleH = ''; 159 if (!empty($settings ['style_heading'])) { 160 $styleH = $settings ['style_heading']; 161 } 162 163 // Determine text styles per level. 164 // Syntax for a style level 1 is "styleL1="color:black;"". 165 // The default style is just 'color:black;'. 166 for ( $count = 0 ; $count < $max_outline_level ; $count++ ) { 167 $stylesL [$count + 1] = 'color:black;'; 168 if (!empty($settings ['styleL'.($count + 1)])) { 169 $stylesL [$count + 1] = $settings ['styleL'.($count + 1)]; 170 } 171 } 172 173 // Create Heading style if not empty. 174 // Default index heading style is taken from styles.xml 175 $title_style = $doc->getStyleName('contents heading'); 176 if (!empty($styleH)) { 177 $properties = array(); 178 $doc->getCSSStylePropertiesForODT ($properties, $styleH); 179 $properties ['style-parent'] = 'Heading'; 180 $properties ['style-class'] = 'index'; 181 $properties ['style-name'] = 'Contents_20_Heading_'.$indexNo; 182 $properties ['style-display-name'] = 'Contents Heading '.$indexNo; 183 $style_obj = ODTParagraphStyle::createParagraphStyle($properties); 184 $doc->addStyle($style_obj); 185 $title_style = $style_obj->getProperty('style-name'); 186 } 187 188 // Create paragraph styles 189 $p_styles = array(); 190 $p_styles_auto = array(); 191 $indent = 0; 192 for ( $count = 0 ; $count < $max_outline_level ; $count++ ) 193 { 194 $indent = $indents [$count]; 195 $properties = array(); 196 $doc->getCSSStylePropertiesForODT ($properties, $stylesL [$count+1]); 197 $properties ['style-parent'] = 'Index'; 198 $properties ['style-class'] = 'index'; 199 $properties ['style-position'] = $settings ['width'] - $indent .'cm'; 200 $properties ['style-type'] = 'right'; 201 $properties ['style-leader-style'] = 'dotted'; 202 $properties ['style-leader-text'] = $leader_sign; 203 $properties ['margin-left'] = $indent.'cm'; 204 $properties ['margin-right'] = '0cm'; 205 $properties ['text-indent'] = '0cm'; 206 $properties ['style-name'] = 'ToC '.$indexNo.'- Level '.($count+1); 207 $properties ['style-display-name'] = 'ToC '.$indexNo.', Level '.($count+1); 208 $style_obj = ODTParagraphStyle::createParagraphStyle($properties); 209 210 // Add paragraph style to common styles. 211 // (It MUST be added to styles NOT to automatic styles. Otherwise LibreOffice will 212 // overwrite/change the style on updating the TOC!!!) 213 $doc->addStyle($style_obj); 214 $p_styles [$count+1] = $style_obj->getProperty('style-name'); 215 216 // Create a copy of that but with parent set to the copied style 217 // and no class 218 $properties ['style-parent'] = $style_obj->getProperty('style-name'); 219 $properties ['style-class'] = NULL; 220 $properties ['style-name'] = 'ToC Auto '.$indexNo.'- Level '.($count+1); 221 $properties ['style-display-name'] = NULL; 222 $style_obj_auto = ODTParagraphStyle::createParagraphStyle($properties); 223 224 // Add paragraph style to automatic styles. 225 // (It MUST be added to automatic styles NOT to styles. Otherwise LibreOffice will 226 // overwrite/change the style on updating the TOC!!!) 227 $doc->addAutomaticStyle($style_obj_auto); 228 $p_styles_auto [$count+1] = $style_obj_auto->getProperty('style-name'); 229 } 230 231 // Create text style for TOC text. 232 // (this MUST be a text style (not paragraph!) and MUST be placed in styles (not automatic styles) to work!) 233 for ( $count = 0 ; $count < $max_outline_level ; $count++ ) { 234 $properties = array(); 235 $doc->getCSSStylePropertiesForODT ($properties, $stylesL [$count+1]); 236 $properties ['style-name'] = 'ToC '.$indexNo.'- Text Level '.($count+1); 237 $properties ['style-display-name'] = 'ToC '.$indexNo.', Level '.($count+1); 238 $style_obj = ODTTextStyle::createTextStyle($properties); 239 $stylesLNames [$count+1] = $style_obj->getProperty('style-name'); 240 $doc->addStyle($style_obj); 241 } 242 243 // Generate ODT toc tag and content 244 switch ($type) { 245 case 'toc': 246 $tag = 'table-of-content'; 247 $name = 'Table of Contents'; 248 $index_name = 'Table of Contents'; 249 $source_attrs = 'text:outline-level="'.$max_outline_level.'" text:use-index-marks="false"'; 250 break; 251 case 'chapter': 252 $tag = 'table-of-content'; 253 $name = 'Table of Contents'; 254 $index_name = 'Table of Contents'; 255 $source_attrs = 'text:outline-level="'.$max_outline_level.'" text:use-index-marks="false" text:index-scope="chapter"'; 256 break; 257 } 258 259 $content = '<text:'.$tag.' text:style-name="Standard" text:protected="true" text:name="'.$name.'">'; 260 $content .= '<text:'.$tag.'-source '.$source_attrs.'>'; 261 if (!empty($title)) { 262 $content .= '<text:index-title-template text:style-name="'.$title_style.'">'.$title.'</text:index-title-template>'; 263 } else { 264 $content .= '<text:index-title-template text:style-name="'.$title_style.'"/>'; 265 } 266 267 // Create TOC templates per outline level. 268 // The styles listed here need to be the same as later used for the headers. 269 // Otherwise the style of the TOC entries/headers will change after an update. 270 for ( $count = 0 ; $count < $max_outline_level ; $count++ ) 271 { 272 $level = $count + 1; 273 $content .= '<text:'.$tag.'-entry-template text:outline-level="'.$level.'" text:style-name="'.$p_styles [$level].'">'; 274 $content .= '<text:index-entry-link-start text:style-name="'.$stylesLNames [$level].'"/>'; 275 $content .= '<text:index-entry-chapter/>'; 276 if ($settings ['numbered_headings'] == true) { 277 $content .= '<text:index-entry-span> </text:index-entry-span>'; 278 } 279 $content .= '<text:index-entry-text/>'; 280 $content .= '<text:index-entry-tab-stop style:type="right" style:leader-char="'.$leader_sign.'"/>'; 281 $content .= '<text:index-entry-page-number/>'; 282 $content .= '<text:index-entry-link-end/>'; 283 $content .= '</text:'.$tag.'-entry-template>'; 284 } 285 286 $content .= '</text:'.$tag.'-source>'; 287 $content .= '<text:index-body>'; 288 if (!empty($title)) { 289 $content .= '<text:index-title text:style-name="Standard" text:name="'.$index_name.'_Head">'; 290 $content .= '<text:p text:style-name="'.$title_style.'">'.$title.'</text:p>'; 291 $content .= '</text:index-title>'; 292 } 293 294 // Add headers to TOC. 295 $page = 0; 296 $links = $settings ['create_links']; 297 if ($type == 'toc') { 298 $content .= self::getTOCBody ($toc, $p_styles_auto, $stylesLNames, $max_outline_level, $links); 299 } else { 300 $startRef = $settings ['start_ref']; 301 $content .= self::getChapterIndexBody ($toc, $p_styles_auto, $stylesLNames, $max_outline_level, $links, $startRef); 302 } 303 304 $content .= '</text:index-body>'; 305 $content .= '</text:'.$tag.'>'; 306 307 // Add a pagebreak if required. 308 if ( $pagebreak ) { 309 $style_name = $doc->createPagebreakStyle(NULL, false); 310 $content .= '<text:p text:style-name="'.$style_name.'"/>'; 311 } 312 313 // Return index content. 314 return $content; 315 } 316 317 /** 318 * This function creates the entries for a table of contents. 319 * All heading are included up to level $max_outline_level. 320 * 321 * @param array $p_styles Array of style names for the paragraphs. 322 * @param array $stylesLNames Array of style names for the links. 323 * @param array $max_outline_level Depth of the table of contents. 324 * @param boolean $links Shall links be created. 325 * @return string TOC body entries 326 */ 327 protected static function getTOCBody(array $toc, $p_styles, $stylesLNames, $max_outline_level, $links) { 328 $page = 0; 329 $content = ''; 330 foreach ($toc as $item) { 331 $params = explode (',', $item); 332 333 // Only add the heading to the TOC if its <= $max_outline_level 334 if ( $params [3] <= $max_outline_level ) { 335 $level = $params [3]; 336 $content .= '<text:p text:style-name="'.$p_styles [$level].'">'; 337 if ( $links == true ) { 338 $content .= '<text:a xlink:type="simple" xlink:href="#'.$params [0].'" text:style-name="'.$stylesLNames [$level].'" text:visited-style-name="'.$stylesLNames [$level].'">'; 339 } 340 $content .= $params [2]; 341 $content .= '<text:tab/>'; 342 $page++; 343 $content .= $page; 344 if ( $links == true ) { 345 $content .= '</text:a>'; 346 } 347 $content .= '</text:p>'; 348 } 349 } 350 return $content; 351 } 352 353 /** 354 * This function creates the entries for a chapter index. 355 * All headings of the chapter are included uo to level $max_outline_level. 356 * 357 * @param array $p_styles Array of style names for the paragraphs. 358 * @param array $stylesLNames Array of style names for the links. 359 * @param array $max_outline_level Depth of the table of contents. 360 * @param boolean $links Shall links be created. 361 * @param string $startRef Reference-ID of chapter main heading. 362 * @return string TOC body entries 363 */ 364 protected static function getChapterIndexBody(array $toc, $p_styles, $stylesLNames, $max_outline_level, $links, $startRef) { 365 $start_outline = 1; 366 $in_chapter = false; 367 $first = true; 368 $content = ''; 369 foreach ($toc as $item) { 370 $params = explode (',', $item); 371 372 if ($in_chapter == true || $params [0] == $startRef ) { 373 $in_chapter = true; 374 375 // Is this the start of a new chapter? 376 if ( $first == false && $params [3] <= $start_outline ) { 377 break; 378 } 379 380 // Only add the heading to the TOC if its <= $max_outline_level 381 if ( $params [3] <= $max_outline_level ) { 382 $level = $params [3]; 383 $content .= '<text:p text:style-name="'.$p_styles [$level].'">'; 384 if ( $links == true ) { 385 $content .= '<text:a xlink:type="simple" xlink:href="#'.$params [0].'" text:style-name="'.$stylesLNames [$level].'" text:visited-style-name="'.$stylesLNames [$level].'">'; 386 } 387 $content .= $params [2]; 388 $content .= '<text:tab/>'; 389 $page++; 390 $content .= $page; 391 if ( $links == true ) { 392 $content .= '</text:a>'; 393 } 394 $content .= '</text:p>'; 395 } 396 $first = false; 397 } 398 } 399 return $content; 400 } 401} 402