Attachment 'SearchInPagesAndSort.py'
Download 1 """
2 MoinMoin - SearchInPagesAndSort Macro
3 A line-oriented search macro over multiple pages, with sorting
4
5 @copyright: Pascal Bauermeister <pascal DOT bauermeister AT hispeed DOT ch>
6 @license: GPL
7
8 Updates:
9 * [v0.3.6] Brecht Fri Jul 18 10:05:21 CEST 2008
10 * Made compatible with MoinMoin 1.6/1.7
11
12 * [v0.3.5pre] Pascal Tue Sep 13 11:33:31 CEST 2005
13 * Added AutoHeading, AutoHeadingFormat
14
15 * [v0.3.4] Pascal Sat Mar 5 17:53:08 CET 2005
16 * MoinMoin 1.3.x _and_ 1.2.x compatible
17 * Added arguments: Format, HeaderFormat and FormatSort
18
19 * [v0.3.3] Pascal
20 * Fixed a security hole (eval used for arguments parsing)
21 * Added argument: ExcludePages=regex
22
23 * [v0.3.2] Pascal
24 * Use StringIO instead of cStringIO, for unicode compatibility
25
26 * [v0.3.1] Pascal Sat Nov 6 16:03:01 CET 2004
27 * Added NoText, RawText, NbSubs and MoreSubsText arguments
28
29 * [v0.3.1] Pascal Mon Aug 30 21:27:36 CEST 2004
30 * Corrected bug: did not work well with multiple pages hit.
31 Bug reported by Craig Johnson.
32 It worked in 0.2.x because one bug corrected another one...
33 * If args are not a kw list (e.g. old macro form) inserts usage in html
34 page (brutal, but we really don't want to support the old form any more)
35
36 * [v0.3.0] Pascal Wed Aug 18 15:39:54 CEST 2004
37 * macro arguments are now passed as a list of KEYWORD=VALUE
38 * ACL is handled
39 * new options: Reverse and NoHeader
40
41 * [v0.2.4] Pascal Mon Jul 19 23:40:54 CEST 2004
42 * Comparisons to None use the 'is' and 'is not' operator (nicer)
43 * Use get() for dict lookup w/ default value
44 * Do not quote args and retry to compile if they are not valid regexes
45 * Corrected usage samples in the comment below
46
47 * [v0.2.3] Pascal Sun Jul 18 13:45:46 CEST 2004
48 Avoid endless recursion when matching page contains this macro
49
50 * [v0.2.2] Fri Jul 16 14:43:23 CEST 2004
51 * Use Request.redirect(). Thanks to Craig Johnson <cpjohnson AT edcon DOT
52 co DOT za>
53 and Thomas Waldmann <tw DASH public AT g m x DOT d e>.
54 * No more unused imports.
55 * Catch only expected exceptions.
56
57 * [v0.2.1] Mon Jun 7 11:54:52 CEST 2004
58 * options: links, heading
59 * works now with MoinMoin Release 1.2 too
60
61 * [v0.1.1] Wed Oct 29 14:48:02 CET 2003
62 works with MoinMoin Release 1.1 [Revision 1.173] and Python 2.3.2
63
64 * [v0.1.0] 2003/04/24 10:32:04
65 Original version
66
67 ----
68
69 Usage:
70 [[ SearchInPagesAndSort ]]
71 [[ SearchInPagesAndSort (KEYWORD=VALUE [, ...] ) ]]
72
73 Search for 'searchtext' regex in pages marching 'pages' regex, and
74 sort the found lines (=hits) in this order:
75 1) substring of the hit matching 'sortkey'; group same matches of
76 'sortkey' by a header
77 2) substring of the hit matching 'searchtext'
78 3) the hit itself
79
80 If no arguments are given, the usage is inserted in the HTML result.
81 Possible keywords:
82
83 Help = 0, 1, 2 Displays 1:short or 2:full help in the page.
84 Default: 0 (i.e. no help).
85
86 Pages = 'PAGES REGEX' Pages in which the text is sought. If
87 empty (default) search in the current page
88 and defaults 'NoLinks' to 1.
89 Default: empty (i.e. current page).
90
91 ExcludePages = 'PAGES REGEX' Exclude these pages (i.e. remove these pages
92 from the list collected by 'Pages').
93 Default: empty (i.e. don't exclude any).
94
95 SearchText = 'TEXT REGEX' To search for lines in matching pages.
96 Mandatory!
97
98 SortKey = 'TEXT REGEX' Criterion to sort matching lines (=hits).
99 Default: empty (i.e. no sorting).
100
101 Heading = 'TEXT REGEX' Follow each hit by the text maching Regex,
102 that preceeds the hit in its source page.
103 Default: empty (i.e. no headings).
104
105 UnassignedText = 'WIKI TEXT' Header for hits not matching the sort key.
106 Default: '[unassigned]'.
107
108 Reverse = 0 or 1 Reverse-sort the hits.
109 Default: 0 (i.e. forward sort).
110
111 RawText = 0 or 1 Do not format found text.
112 Default: 0 (i.e. formatted).
113
114 Format = 'STRING' Explicitely format the output using this
115 string, which can contain wiki formatting
116 as well as these tokens:
117 @KT@ : text matching 'SortKey'
118 @ST@ : text matching 'SearchText'
119 @FT@ : line of text
120 @PN@ : page name
121 @HT@ : heading text
122 @SU@ : subtext
123 @@ : the '@' character
124 \\n : newline (of wiki source text).
125
126 Each token can contain a regex acting as
127 a filter for displaying the value, e.g:
128 @FT:{[123]}@ displays the prio smiley
129
130 Multiple groups can be defined, in which
131 case the text matching them will be
132 displayed, e.g:
133 @FT:{[123]}(.*)@ displays text after prio
134
135 Default: '' (i.e. auto-formatting).
136
137 HeaderFormat = 'STRING' If specified, use this instead of 'Format'
138 for headers.
139 Default: '' (i.e. do not display headers).
140
141 FormatSort = 0 or 1 If 1, sort the output generated by 'Format'
142 (if 'Reverse' is 1, reverse-sort). If 0,
143 leave the output sorted by the 'SortKey'
144 criterion (if specified).
145 Default: 0 (i.e. no sorting).
146
147 Unique = 0 or 1 If 1, make formatted output lines unique.
148 Default: 0 (i.e. no filtering).
149
150 NbSubs = 0, N, or 'all' Follow each hit by max N sub lines (i.e.
151 next lines with greater indent) of source
152 text following the hit. If N is 'all', take
153 *all* sub lines. If N is positive, take N
154 *first* sub lines. If N is negative, take
155 the |N| *last* sub lines.
156 Default: 0 (i.e. do *not* include subs).
157
158 MoreSubsText = 'WIKI TEXT' If there are more sub lines than 'NbSubs',
159 follow/preceed the last/first sub lines by
160 this text.
161 Default: '...'.
162
163 NoHeader = 0 or 1 Disable showing the headers as subtitles.
164 Default: 0 (i.e. show headers).
165
166 NoLinks = 0 or 1 Disable following each hit by a link to its
167 page.
168 Default: 0 (i.e. show links) or 1 if
169 'Pages' is omitted.
170
171 NoPageText = 'HTML TEXT' Text displayed if no page match 'Pages'.
172 Default: an error message w/ Page regex
173
174 NoText = 0 or 1 Disables showing the found text.
175 Default: 0 (i.e. show found text).
176
177 Keywords can be also given in upper or lower cases, or abbreviated.
178 Example: SearchText, searchtext, SEARCHTEXT, st, ST, Pages, p, etc.
179
180 ----
181
182 Sample 1:
183
184 Given a page named 'ProjectA':
185 1. Action Items
186 1. [Alan] {2} to launch this task
187 1. [Alan] {1} to do this urgent thing
188 1. [Ben][Clara] {3} do this as background task
189
190 1. Deadlines
191 1. 2003-03-12 <!> [Alan][Clara]: deliver 1st version of the Release X
192
193 ...and a page named 'ProjectB':
194 * [Denise] {2} Development of task Xyz
195 * [Eric] {1} Tests of feature F
196 * [Eric] (./) Tests of feature E
197
198 ...using the macro in a page named 'ActionItems' like this:
199 = ActionItems =
200 [[SearchInPagesAndSort(pages="Project.*", searchtext="{[123]}", sortkey="\[[A-Za-z_]*\]")]]
201
202 = Deadlines =
203 [[SearchInPagesAndSort(pages="Project.*", searchtext="<!>")]]
204
205 = Completed tasks =
206 [[SearchInPagesAndSort(pages="Project.*", searchtext"(\./)", sortkey="\[[A-Za-z_]*\]")]]
207
208 ...will give this output (note: _text_ are links):
209 ActionItems
210 * [Alan]
211 * [Alan] {1} to do this urgent thing _ProjectA_
212 * [Alan] {2} to launch this task _ProjectA_
213 * [Denise]
214 * [Denise] {2} Development of task Xyz _ProjectB_
215 * [Ben]
216 * [Ben][Clara] {3} do this as background task _ProjectA_
217 * [Eric]
218 * [Eric] {1} Tests of feature F _ProjectB_
219 * [Clara]
220 * [Ben][Clara] {3} do this as background task _ProjectA_
221
222 Deadlines
223 * 2003-03-12 <!> [Alan][Clara]: deliver 1st version of the Release X
224 _ProjectA_
225
226 Completed tasks
227 * [Eric]
228 * [Eric] (./) Tests of feature E _ProjectB_
229
230
231 Sample 2:
232
233 Given a page containing:
234 == Tasks for (ABC) ==
235 * {1} (due:2003-12-16) [Mike] Do this
236 == Tasks for (XYZ) ==
237 * {2} (due:2003-12-17) [John_Doe][Mike] Do that
238
239 ...the following macro call in the same page:
240 [[SearchInPagesAndSort(searchtext="{[123]}", sortkey="\[[A-Za-z_ -]*\]", links=0, heading="\([ab]*[0-9][0-9][0-9]\)")]]
241
242 ...will produce:
243 * [John_Doe]
244 * {2} (due:2003-12-17) [John_Doe][Mike] Do that (XYZ)
245
246 * [Mike]
247 * {1} (due:2003-12-16) [Mike] Do this (ABC)
248 * {2} (due:2003-12-17) [John_Doe][Mike] Do that (XYZ)
249 """
250
251 # Imports
252 import re, sys, StringIO, urllib
253 from string import ascii_lowercase, maketrans
254 from MoinMoin import config, wikiutil, version
255 from MoinMoin.Page import Page
256 from MoinMoin.parser import text_moin_wiki
257
258 before_1_3 = version.release < '1.3'
259
260 Dependencies = ["time"] # macro cannot be cached
261
262 _recursions = 0
263 FAKETRANS = maketrans ("","")
264
265
266 class _Error (Exception):
267 pass
268
269
270 def execute (macro, text, args_re=None):
271
272 global _recursions
273 if _recursions: return ''
274
275 _recursions += 1
276 try: res = _execute (macro, text)
277 except _Error, msg:
278 _recursions = 0
279 return """
280 <p><strong class="error">
281 Error: macro SearchInPagesAndSort: %s</strong> </p>
282 """ % msg
283
284 _recursions -=1
285 return res
286
287
288 def _delparam (keyword, params):
289 value = params [keyword]
290 del params [keyword]
291 return value
292
293
294 def _param_get (params, spec, default):
295
296 """Returns the value for a parameter, if specified with one of
297 several acceptable keyword names, or returns its default value if
298 it is missing from the macro call. If the parameter is specified,
299 it is removed from the list, so that remaining params can be
300 signalled as unknown"""
301
302 # param name is litteral ?
303 if params.has_key (spec): return _delparam (spec, params)
304
305 # param name is all lower or all upper ?
306 lspec = spec.lower ()
307 if params.has_key (lspec): return _delparam (lspec, params)
308 uspec = spec.upper ()
309 if params.has_key (uspec): return _delparam (uspec, params)
310
311 # param name is abbreviated ?
312 cspec = spec [0].upper () + spec [1:] # capitalize 1st letter
313 cspec = cspec.translate (FAKETRANS, ascii_lowercase)
314 if params.has_key (cspec): return _delparam (cspec, params)
315 cspec = cspec.lower ()
316 if params.has_key (cspec): return _delparam (cspec, params)
317
318 # nope: return default value
319 return default
320
321
322 def _usage (full = False):
323
324 """Returns the interesting part of the module's doc"""
325
326 if full: return __doc__
327
328 lines = __doc__.replace ('\\n', '\\\\n'). splitlines ()
329 start = 0
330 end = len (lines)
331 for i in range (end):
332 if lines [i].strip ().lower () == "usage:":
333 start = i
334 break
335 for i in range (start, end):
336 if lines [i].startswith ('--'):
337 end = i
338 break
339 return '\n'.join (lines [start:end])
340
341
342 def _re_compile (text, name):
343 try:
344 return re.compile (text, re.IGNORECASE)
345 except Exception, msg:
346 raise _Error ("%s for regex argument %s: '%s'" % (msg, name, text))
347
348
349 def _indent_of (line, pos=0):
350 n = 0
351 for c in line [pos:]:
352 if c != ' ': break
353 n = n + 1
354 return n
355
356
357 last_request_h = None
358 last_pages_list = []
359
360 def _get_all_pages (request):
361 global last_request_h
362 global last_pages_list
363 request_h = hash (request)
364 if request_h != last_request_h:
365 if before_1_3: all_pages = wikiutil.getPageList (config.text_dir)
366 else: all_pages = request.rootpage.getPageList()
367 last_request_h = request_h
368 last_pages_list = all_pages
369 return last_pages_list
370
371
372 def _subtext_get (body, pos, nbsubs, indent, moresubs):
373 # dirty hack to remove empty lines
374 text = body [pos:]
375 l = 0
376 while l != len(text):
377 l = len (text)
378 text = text.replace(' \n', '\n')
379 l = 0
380 while l != len(text):
381 l = len (text)
382 text = text.replace('\n\n', '\n')
383 text = body [:pos] + text
384
385 subpos = pos+1
386 end = len (text)
387 lead = ' '*indent
388 while True:
389 if subpos>=end: break
390 ind = _indent_of (text, subpos)
391 if ind <= indent: break
392 p = text.find ("\n", subpos)
393 if p == -1: break
394 else: subpos = p + 1
395 subs = text [pos:subpos].strip ('\n').split ('\n')
396 ls = len (subs)
397 if (nbsubs=='all'): pass
398 elif nbsubs>0 and ls>nbsubs:
399 subs = subs [0:nbsubs]
400 subs.append (lead + moresubs)
401 elif nbsubs<0 and ls>-nbsubs:
402 subs = subs [nbsubs:]
403 subs.insert (0, lead + moresubs)
404 lead + '\n'.join (subs)
405 return lead + '\n'.join (subs)
406
407
408 # The "raison d'etre" of this module
409 def _execute (macro, text):
410
411 result = ""
412
413 # new args syntax
414 try:
415 params = eval ("(lambda **opts: opts)(%s)" % text,
416 {'__builtins__': []}, {})
417 except Exception, msg:
418 raise _Error ("""<pre>malformed arguments list:
419 %s<br>cause:
420 %s
421 </pre>
422 <br> usage:
423 <pre>%s</pre>
424 """ % (text, msg, _usage () ) )
425
426 arg_text = _param_get (params, 'SearchText', None)
427 arg_pages = _param_get (params, 'Pages', '')
428 arg_excl_pages = _param_get (params, 'ExcludePages', '')
429 arg_key = _param_get (params, 'SortKey', None)
430
431 opt_heading = _param_get (params, 'Heading', None)
432 opt_unassigned_text = _param_get (params, 'UnassignedText',
433 "[unassigned]")
434 opt_reverse = _param_get (params, 'Reverse', False)
435 opt_rawtext = _param_get (params, 'RawText', False)
436
437 opt_format = _param_get (params, 'Format', '')
438 opt_headerformat = _param_get (params, 'HeaderFormat', '')
439 opt_formatsort = _param_get (params, 'FormatSort', 0)
440 opt_unique = _param_get (params, 'Unique', 0)
441
442 opt_autoheading = _param_get (params, 'AutoHeading', '')
443 opt_autoheadingformat= _param_get (params, 'AutoHeadingFormat', '')
444
445 def_nolinks = (1,0) [len (arg_pages)>0]
446 opt_nolinks = _param_get (params, 'NoLinks', def_nolinks)
447 opt_noheader = _param_get (params, 'NoHeader', False)
448 opt_notext = _param_get (params, 'NoText', False)
449 opt_nopage = _param_get (params, 'NoPageText', None)
450 opt_help = _param_get (params, 'Help', 0)
451
452 opt_nbsubs = _param_get (params, 'NbSubs', 0)
453 def_moresubs = ('...', None) [opt_nbsubs=='all']
454 opt_moresubs = _param_get (params, 'MoreSubsText', def_moresubs)
455
456 # help ?
457 if opt_help:
458 return """
459 <p>
460 Macro SearchInPagesAndSort usage:
461 <pre>%s</pre></p>
462 """ % _usage (opt_help==2)
463
464 # check the args a little bit
465 if len (params):
466 raise _Error ("""unknown argument(s): %s
467 <br> usage:
468 <pre>%s</pre>
469 """ % (`params.keys ()`, _usage () ) )
470
471 if arg_text is None:
472 raise _Error ("missing 'searchtext' argument")
473
474 # empty page means this page; subpage are also handled
475 if len (arg_pages) == 0 or arg_pages.startswith ('/'):
476 arg_pages = macro.formatter.page.page_name + arg_pages
477
478 # get a list of pages matching the PageRegex
479 pages_re = _re_compile (arg_pages, 'Pages')
480 all_pages = _get_all_pages (macro.request)
481 hits = filter (pages_re.search, all_pages)
482 if arg_excl_pages:
483 excl_pages_re = _re_compile (arg_excl_pages, 'ExcludePages')
484 hits = filter (lambda hit: not excl_pages_re.search (hit), hits)
485
486 if before_1_3:
487 # check ACL now (since we may end up with no pages)
488 if config.acl_enabled:
489 me = macro.request.user.name
490 def _check_page (page_name):
491 page = Page (page_name) # too bad we must instanciate...
492 return page.getACL ().may (macro.request, me, "read")
493 hits = filter (_check_page, hits)
494
495 # sort pages, check if we have pages
496 if len (hits) == 0:
497 if opt_nopage: return "%s" % opt_nopage
498 else:
499 raise _Error ("no page matching '%s'!" % arg_pages)
500 else: hits.sort ()
501
502 # compile all regex
503 text_re = _re_compile (arg_text, 'SearchText')
504
505 if arg_key is not None:
506 key_re = _re_compile (arg_key, 'SortKey')
507
508 if opt_heading is not None:
509 heading_re = _re_compile (opt_heading, 'Heading')
510
511 # we will collect matching lines in each matching page
512 all_matches = []
513
514 # treat each found page
515 for page_name in hits:
516 if before_1_3: body = Page (page_name).get_raw_body ()
517 else: body = Page (macro.request, page_name).get_raw_body ()
518 pos = 0
519 last_start = -1
520 last_end = -1
521 heading_text = ""
522 while 1:
523 keep_line = 1
524
525 # search text
526 match = text_re.search (body, pos)
527 if not match: break
528
529 # text is found; now search for heading
530 if opt_heading is not None:
531 heading_pos = pos
532 heading_match = True
533 # keep the nearest heading to the found text
534 while heading_match:
535 heading_match = heading_re.search (body, heading_pos)
536 if heading_match and \
537 heading_match.start () < match.start ():
538 heading_text = heading_match.group (0)
539 heading_pos = heading_match.end ()
540 else: heading_match = False
541
542 # point to found text
543 pos = match.end ()+1
544
545 # cut before start of line
546 start_pos = match.start ()
547 rev = 0
548 while body [start_pos] != '\n' and start_pos:
549 start_pos = start_pos - 1
550 rev = 1
551 if rev:
552 start_pos = start_pos + 1
553
554 # cut at end of line
555 end_pos = body.find ("\n", match.end ())
556
557 # extract line
558 raw_line = body [start_pos:end_pos]
559 indent = _indent_of (raw_line)
560 line = raw_line.strip ()
561
562 # store this record if it differs from previous one
563 if start_pos == last_start or end_pos == last_end: keep_line = 0
564
565 # store this record if it it is not a comment
566 elif line.startswith ("##"): keep_line = 0
567
568 # remove possible list item leaders
569 if keep_line:
570 for heading in ["*", "1.", "a.", "A.", "i.", "I."]:
571 if line.startswith (heading):
572 line = line.replace (heading, "", 1)
573 line = line.strip ()
574 if len (line)==0: keep_line = 0
575
576 # handle this record
577 if keep_line:
578
579 # get sub sections
580 if opt_nbsubs:
581 subtext = '\n' + _subtext_get (body, end_pos, opt_nbsubs,
582 indent, opt_moresubs)
583 else: subtext = ''
584
585 # find the sort key
586 nbmatches = 0
587 keypos = 0
588 found = 0
589 while 1:
590 if arg_key is None:
591 keyval = ""
592 else:
593 keymatch = key_re.search (line, keypos)
594 if keymatch:
595 keyval = line [keymatch.start ():keymatch.end ()]
596 keypos = keymatch.end ()
597 nbmatches = nbmatches + 1
598 found = 1
599 else:
600 if nbmatches>0: break
601 keyval = opt_unassigned_text
602
603 # store info
604 item = []
605 def append (txt): item.append (txt.strip ())
606 def append_rstrip (txt): item.append (txt.rstrip ())
607 append (keyval) # key text
608 append (body [match.start ():match.end ()]) # srch txt
609 append (line) # line text
610 append (page_name) # page name
611 append (heading_text) # heading
612 append_rstrip (subtext) # subsections
613 all_matches.append (item)
614 if found == 0: break
615
616 last_start = start_pos
617 last_end = end_pos
618
619 # all occurences of sort key found
620 # this line handled
621 # all lines handled
622 # all pages handled
623
624 # prepare some formatting text
625 bullet_list_open = macro.formatter.bullet_list (1)
626 bullet_list_close = macro.formatter.bullet_list (0)
627 listitem_open = macro.formatter.listitem (1)
628 listitem_close = macro.formatter.listitem (0)
629
630 # now sort and format records
631 if not opt_notext and not opt_reverse: all_matches.sort ()
632 if opt_reverse: all_matches.reverse ()
633
634 # explicitely-formatted output
635 if opt_format:
636 block = ""
637 last_keytext = None
638 rx = re.compile (r'([^@]*?)(@[^@]*?@)')
639 pairs = re.findall (rx, opt_format+"@-@")
640 if opt_headerformat: hpairs = re.findall (rx, opt_headerformat+"@-@")
641 else: hpairs = None
642 rx2d = {}
643 for item in all_matches:
644 keytext, srchtext, text, pagename, heading_text, subtext = item
645 subtext = subtext.replace ("\n", "[[BR]]")
646 if keytext == last_keytext: plist = (pairs,)
647 elif hpairs: plist = (hpairs, pairs)
648 else: plist = (pairs,)
649 last_keytext = keytext
650 for p in plist:
651 for txt, token in p:
652 txt = txt.replace ("\\n", "\n")
653 if not token: continue
654 token = token.strip ("@")
655 block += txt
656 rx2 = None
657 if len (token)>2 and token [2]==":":
658 token, rx2 = token [:2], token [3:]
659 if not rx2d.has_key (rx2): rx2d [rx2] = \
660 re.compile (rx2)
661 rx2 = rx2d [rx2]
662 token = token.replace ("\\n", "\n")
663 d = { "KT": keytext, "ST": srchtext,
664 "FT": text, "PN": pagename,
665 "HT": heading_text, "SU": subtext,
666 "": "@",
667 "-": "",
668 }
669 if rx2:
670 tx = d.get (token, None)
671 if tx:
672 tx = map ("".join, re.findall (rx2, tx))
673 if tx: tx = tx [0]
674 else: tx = ""
675 else: tx = token
676 block += tx
677 else:
678 block += d.get (token, token)
679 # sort lines
680 if opt_formatsort or opt_reverse or opt_unique:
681 lines = block.split ("\n")
682 if opt_formatsort: lines.sort ()
683 if opt_reverse: lines.reverse ()
684 if opt_unique:
685 newlines = []
686 last = None
687 for l in lines:
688 if l != last: newlines.append(l)
689 last = l
690 lines = newlines
691 block = "\n".join (lines)
692
693 # now generate auto-headers
694
695 # format lines
696 result += "\n%s\n" % _format (block, macro.request, macro.formatter)
697
698 # auto-formatted output treat records for output
699 else:
700 head_count = 0
701 result = result+"\n" + bullet_list_open
702 keyval = ""
703 last_pagename = ""
704 for item in all_matches:
705 keytext, srchtext, text, pagename, heading_text, subtext = item
706
707 if opt_notext:
708 text_fmtted = ""
709 if last_pagename == pagename: continue
710 else: last_pagename = pagename
711 elif opt_rawtext:
712 text_fmtted = wikiutil.escape (text)
713 else:
714 # parse the text (in wiki source format) and make HTML,
715 # after diverting sys.stdout to a string
716 text_fmtted = _format (text, macro.request, macro.formatter)
717 text_fmtted = text_fmtted.strip (' ') # preserve newlines
718
719 # empty text => drop this item
720 if len (text_fmtted)==0: continue
721
722 # insert heading (only if not yet done)
723 if not opt_noheader \
724 and arg_key is not None \
725 and keytext != keyval:
726 # this is a new heading
727 keyval = keytext
728 if head_count:
729 result = result+"\n " + bullet_list_close
730 result = result+"\n " + listitem_close
731 head_count = head_count +1
732 result = result+"\n " + listitem_open
733 result = result+ _format (keyval,
734 macro.request, macro.formatter)
735 result = result+"\n " + bullet_list_open
736
737 # correct the text format (berk)
738 if text_fmtted.startswith ("\n<p>"):
739 text_fmtted = text_fmtted [4:]
740 if text_fmtted.endswith ("</p>\n"):
741 text_fmtted = text_fmtted [:-5]
742 text_trailer = "\n</p>\n"
743 else: text_trailer = ""
744
745 # insert formatted text
746 result = result+"\n " + listitem_open
747 result = result + text_fmtted
748 if not opt_nolinks:
749 result = result + " <font size=-1>["
750 if arg_text:
751 if before_1_3:
752 pageurl = '%s?action=highlight&value=%s' % (
753 pagename,
754 urllib.quote_plus (re.escape (text)))
755 else:
756 pageurl = pagename
757 # HACKX!
758 # pageurl = '%s?highlight=%s' % (
759 # pagename,
760 # urllib.quote_plus (re.escape (text)))
761
762 else: pageurl = wikiutil.quoteWikiname (pagename)
763 link_text = wikiutil.link_tag (macro.request,
764 pageurl, pagename)
765
766 result = result + link_text
767 result = result + "]</font>"
768 if opt_heading is not None:
769 result = result + " <font size=-1>["
770 result = result + heading_text
771 result = result + "]</font>"
772
773 if opt_nbsubs:
774 result = result + _format (subtext,
775 macro.request, macro.formatter)
776
777 result = result + text_trailer + "\n " + listitem_close
778
779 # all items done, close (hopefully) gracefully
780 if not opt_format:
781 if head_count:
782 result = result+"\n " + listitem_close
783 result = result+"\n " + bullet_list_close
784 if not opt_noheader and arg_key is not None:
785 result = result+"\n " + listitem_close
786 result = result+"\n" + bullet_list_close
787
788 # done
789 return result
790
791 def _format (src_text, request, formatter):
792 # parse the text (in wiki source format) and make HTML,
793 # after diverting sys.stdout to a string
794 str_out = StringIO.StringIO () # create str to collect output
795 request.redirect (str_out) # divert output to that string
796 # parse this line
797 text_moin_wiki.Parser (src_text, request).format (formatter)
798 request.redirect () # restore output
799 return str_out.getvalue () # return what was generated
Attached Files
To refer to attachments on a page, use attachment:filename, as shown below in the list of files. Do NOT use the URL of the [get] link, since this is subject to change and can break easily.You are not allowed to attach a file to this page.