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