5 # (c) 2007-2008 Michael Rentzsch (http://www.repc.de)
6 # (c) 2009-2011 Michael Rentzsch (http://www.repc.de)
7 # Kai Dietrich (mail@cleeus.de)
9 # Create latex beamer sources for multiple frames from a wiki-like code.
12 # This file is part of wiki2beamer.
13 # wiki2beamer is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 2 of the License, or
16 # (at your option) any later version.
18 # wiki2beamer is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
23 # You should have received a copy of the GNU General Public License
24 # along with wiki2beamer. If not, see <http://www.gnu.org/licenses/>.
26 # Additional commits by:
27 # Valentin Haenel <valentin.haenel@gmx.de>
28 # Julius Plenz <julius@plenz.com>
39 __version__= VERSIONTAG
40 __author__= "Michael Rentzsch, Kai Dietrich and others"
42 #python 2.4 compatability
43 if sys.version_info >= (2, 5):
47 #python 2.4 compatability
49 if sys.version_info >= (2, 5):
50 return hashlib.md5(string).hexdigest()
56 _redirected_stdout = None
57 _redirected_stderr = None
58 def pprint(string, file=sys.stdout, eol=True):
59 ''' portable version of print which directly writes into the given stream '''
60 if file == sys.stdout and _redirected_stdout is not None:
61 file = _redirected_stdout
62 if file == sys.stderr and _redirected_stderr is not None:
63 file = _redirected_stderr
67 file.write(os.linesep)
71 """ print debug message to stderr """
72 pprint(message, file=sys.stderr)
74 def syntax_error(message, code):
75 pprint('syntax error: %s' % message, file=sys.stderr)
76 pprint('\tcode:\n%s' % code, file=sys.stderr)
79 class IncludeLoopException(Exception):
85 basicstyle=\footnotesize\ttfamily,%
91 showstringspaces=false,%
94 keywordstyle=\color{blue},%
96 commentstyle=\color{gray},%
97 stringstyle=\color{magenta}%
101 ('documentclass', '{beamer}'),
102 ('usepackage', '{listings}'),
103 ('usepackage', '{wasysym}'),
104 ('usepackage', '{graphicx}'),
105 ('date', '{\\today}'),
106 ('lstdefinestyle', lstbasicstyle),
107 ('titleframe', 'True'),
110 nowikistartre = re.compile(r'^<\[\s*nowiki\s*\]')
111 nowikiendre = re.compile(r'^\[\s*nowiki\s*\]>')
112 codestartre = re.compile(r'^<\[\s*code\s*\]')
113 codeendre = re.compile(r'^\[\s*code\s*\]>')
115 # lazy initialisation cache for file content
118 def add_lines_to_cache(filename, lines):
119 if not filename in _file_cache:
120 _file_cache[filename] = lines
122 def get_lines_from_cache(filename):
123 if filename in _file_cache:
124 return _file_cache[filename]
126 lines = read_file_to_lines(filename)
127 _file_cache[filename] = lines
130 def clear_file_cache():
134 from collections import OrderedDict as maybe_odict
140 self.frame_opened = False
141 self.enum_item_level = ''
142 self.frame_header = ''
143 self.frame_footer = ''
144 self.next_frame_footer = ''
145 self.next_frame_header = ''
146 self.current_line = 0
147 self.autotemplate_opened = False
148 self.defverbs = maybe_odict()
152 def switch_to_next_frame(self):
153 self.frame_header = self.next_frame_header
154 self.frame_footer = self.next_frame_footer
157 def escape_resub(string):
158 p = re.compile(r"\\")
159 return p.sub(r"\\\\", string)
162 def transform_itemenums(string, state):
163 """handle itemizations/enumerations"""
164 preamble = "" # for enumeration/itemize environment commands
166 # handle itemizing/enumerations
167 p = re.compile("^([\*\#]+).*$")
170 my_enum_item_level = ""
172 my_enum_item_level = m.group(1)
174 # trivial: old level = new level
175 if my_enum_item_level == state.enum_item_level:
180 while (len(state.enum_item_level) > common + 1) and \
181 (len(my_enum_item_level) > common + 1) and \
182 (state.enum_item_level[common+1] == my_enum_item_level[common+1]):
185 # close enum_item_level environments from back to front
186 for i in range(len(state.enum_item_level)-1, common, -1):
187 if state.enum_item_level[i] == "*":
188 preamble = preamble + "\\end{itemize}\n"
189 elif state.enum_item_level[i] == "#":
190 preamble = preamble + "\\end{enumerate}\n"
192 # open my_enum_item_level environments from front to back
193 for i in range(common+1, len(my_enum_item_level)):
194 if my_enum_item_level[i] == "*":
195 preamble = preamble + "\\begin{itemize}\n"
196 elif my_enum_item_level[i] == "#":
197 preamble = preamble + "\\begin{enumerate}\n"
198 state.enum_item_level = my_enum_item_level
200 # now, substitute item markers
201 p = re.compile("^([\*\#]+)\s*(.*)$")
202 _string = p.sub(r" \\item \2", string)
203 string = preamble + _string
207 def transform_define_foothead(string, state):
208 """ header and footer definitions"""
209 p = re.compile("^@FRAMEHEADER=(.*)$", re.VERBOSE)
212 state.next_frame_header = m.group(1)
214 p = re.compile("^@FRAMEFOOTER=(.*)$", re.VERBOSE)
217 state.next_frame_footer = m.group(1)
221 def transform_detect_manual_frameclose(string, state):
222 """ detect manual closing of frames """
223 p = re.compile(r"\[\s*frame\s*\]>")
224 if state.frame_opened:
225 if p.match(string) is not None:
226 state.frame_opened = False
229 def get_frame_closing(state):
230 return " %s \n\\end{frame}\n" % state.frame_footer
232 def transform_h4_to_frame(string, state):
233 """headings (3) to frames"""
234 frame_opening = r"\\begin{frame}\2\n \\frametitle{\1}\n %s \n" % escape_resub(state.next_frame_header)
235 frame_closing = escape_resub(get_frame_closing(state))
237 p = re.compile("^!?====\s*(.*?)\s*====(.*)", re.VERBOSE)
238 if not state.frame_opened:
239 _string = p.sub(frame_opening, string)
241 _string = p.sub(frame_closing + frame_opening, string)
243 if string != _string:
244 state.frame_opened = True
245 state.switch_to_next_frame()
249 def transform_h3_to_subsec(string, state):
250 """ headings (2) to subsections """
251 frame_closing = escape_resub(get_frame_closing(state))
252 subsec_opening = r"\n\\subsection\2{\1}\n\n"
254 p = re.compile("^===\s*(.*?)\s*===(.*)", re.VERBOSE)
255 if state.frame_opened:
256 _string = p.sub(frame_closing + subsec_opening, string)
258 _string = p.sub(subsec_opening, string)
259 if string != _string:
260 state.frame_opened = False
264 def transform_h2_to_sec(string, state):
265 """ headings (1) to sections """
266 frame_closing = escape_resub(get_frame_closing(state))
267 sec_opening = r"\n\\section\2{\1}\n\n"
268 p = re.compile("^==\s*(.*?)\s*==(.*)", re.VERBOSE)
269 if state.frame_opened:
270 _string = p.sub(frame_closing + sec_opening, string)
272 _string = p.sub(sec_opening, string)
273 if string != _string:
274 state.frame_opened = False
278 def transform_replace_headfoot(string, state):
279 string = string.replace("<---FRAMEHEADER--->", state.frame_header)
280 string = string.replace("<---FRAMEFOOTER--->", state.frame_footer)
283 def transform_environments(string):
285 latex environments, the users takes full responsibility
286 for closing ALL opened environments
288 <[block]{block title}
293 p = re.compile("^<\[([^{}]*?)\]", re.VERBOSE)
294 string = p.sub(r"\\begin{\1}", string)
296 p = re.compile("^\[([^{}]*?)\]>", re.VERBOSE)
297 string = p.sub(r"\\end{\1}", string)
301 def transform_columns(string):
303 p = re.compile("^\[\[\[(.*?)\]\]\]", re.VERBOSE)
304 string = p.sub(r"\\column{\1}", string)
307 def transform_boldfont(string):
309 p = re.compile("'''(.*?)'''", re.VERBOSE)
310 string = p.sub(r"\\textbf{\1}", string)
313 def transform_italicfont(string):
315 p = re.compile("''(.*?)''", re.VERBOSE)
316 string = p.sub(r"\\emph{\1}", string)
319 def _transform_mini_parser(character, replacement, string):
320 # implemented as a state-machine
321 output, typewriter = [], []
322 seen_at, seen_escape = False, False
325 if char == character:
326 output.append(character)
328 output.append('\\' + char)
332 elif char == character:
335 output, typewriter = typewriter, output
336 output.append('\\'+replacement+'{')
342 output, typewriter = typewriter, output
346 output, typewriter = typewriter, output
347 output.append(character)
349 return "".join(output)
351 def transform_typewriterfont(string):
352 """ typewriter font """
353 return _transform_mini_parser('@', 'texttt', string)
355 def transform_alerts(string):
357 return _transform_mini_parser('!', 'alert', string)
359 def transform_colors(string):
361 def maybe_replace(m):
362 """ only replace if we are not within <<< >>> """
364 # found color is within a graphics token
365 if m.start() >= g.start() and m.end() <= g.end():
366 return m.string[m.start():m.end()]
368 return "\\textcolor{" + m.group(1) + "}{" + m.group(2) + "}"
370 p = re.compile("(\<\<\<)(.*?)\>\>\>", re.VERBOSE)
371 graphics = list(p.finditer(string))
372 p = re.compile("_([^_\\\\{}]*?)_([^_]*?[^_\\\\{}])_", re.VERBOSE)
373 string = p.sub(maybe_replace, string)
376 def transform_footnotes(string):
378 p = re.compile("\(\(\((.*?)\)\)\)", re.VERBOSE)
379 string = p.sub(r"\\footnote{\1}", string)
382 def transform_graphics(string):
383 """ figures/images """
384 p = re.compile("\<\<\<(.*?),(.*?)\>\>\>", re.VERBOSE)
385 string = p.sub(r"\\includegraphics[\2]{\1}", string)
386 p = re.compile("\<\<\<(.*?)\>\>\>", re.VERBOSE)
387 string = p.sub(r"\\includegraphics{\1}", string)
390 def transform_substitutions(string):
391 """ substitutions """
392 p = re.compile("(\s)-->(\s)", re.VERBOSE)
393 string = p.sub(r"\1$\\rightarrow$\2", string)
394 p = re.compile("(\s)<--(\s)", re.VERBOSE)
395 string = p.sub(r"\1$\\leftarrow$\2", string)
396 p = re.compile("(\s)==>(\s)", re.VERBOSE)
397 string = p.sub(r"\1$\\Rightarrow$\2", string)
398 p = re.compile("(\s)<==(\s)", re.VERBOSE)
399 string = p.sub(r"\1$\\Leftarrow$\2", string)
400 p = re.compile("(\s):-\)(\s)", re.VERBOSE)
401 string = p.sub(r"\1\\smiley\2", string)
402 p = re.compile("(\s):-\((\s)", re.VERBOSE)
403 string = p.sub(r"\1\\frownie\2", string)
406 def transform_vspace(string):
408 p = re.compile("^\s*--(.*)--\s*$")
409 string = p.sub(r"\n\\vspace{\1}\n", string)
412 def transform_vspacestar(string):
414 p = re.compile("^\s*--\*(.*)--\s*$")
415 string = p.sub(r"\n\\vspace*{\1}\n", string)
418 def transform_uncover(string):
420 p = re.compile("\+<(.*)>\s*{(.*)") # +<1-2>{.... -> \uncover<1-2>{....
421 string = p.sub(r"\uncover<\1>{\2", string)
424 def transform_only(string):
426 p = re.compile("-<(.*)>\s*{(.*)") # -<1-2>{.... -> \only<1-2>{....
427 string = p.sub(r"\only<\1>{\2", string)
430 def transform(string, state):
431 """ convert/transform one line in context of state"""
433 #string = transform_itemenums(string, state)
434 string = transform_define_foothead(string, state)
435 string = transform_detect_manual_frameclose(string, state)
436 string = transform_h4_to_frame(string, state)
437 string = transform_h3_to_subsec(string, state)
438 string = transform_h2_to_sec(string, state)
439 string = transform_replace_headfoot(string, state)
441 string = transform_environments(string)
442 string = transform_columns(string)
443 string = transform_boldfont(string)
444 string = transform_italicfont(string)
445 string = transform_typewriterfont(string)
446 string = transform_alerts(string)
447 string = transform_colors(string)
448 string = transform_footnotes(string)
449 string = transform_graphics(string)
450 string = transform_substitutions(string)
451 string = transform_vspacestar(string)
452 string = transform_vspace(string)
453 string = transform_uncover(string)
454 string = transform_only(string)
456 string = transform_itemenums(string, state)
460 def expand_code_make_defverb(content, name):
461 return "\\defverbatim[colored]\\%s{\n%s\n}" % (name, content)
463 def expand_code_make_lstlisting(content, options):
464 return "\\begin{lstlisting}%s%s\\end{lstlisting}" % (options, content)
466 def expand_code_search_escape_sequences(code):
469 while code.find(open) != -1 or code.find(close) != -1:
470 open = open + chr(random.randint(48,57))
471 close = close + chr(random.randint(48,57))
475 def expand_code_tokenize_anims(code):
477 (esc_open, esc_close) = expand_code_search_escape_sequences(code)
478 code = code.replace('\\[', esc_open)
479 code = code.replace('\\]', esc_close)
481 p = re.compile(r'\[\[(?:.|\s)*?\]\]|\[(?:.|\s)*?\]')
482 non_anim = p.split(code)
483 anim = p.findall(code)
486 anim = [s.replace(esc_open, '\\[').replace(esc_close, '\\]') for s in anim]
487 non_anim = [s.replace(esc_open, '[').replace(esc_close, ']') for s in non_anim]
489 return (anim, non_anim)
491 def make_unique(seq):
492 '''remove duplicate elements in a list, does not preserve order'''
496 return list(keys.keys())
498 def expand_code_parse_overlayspec(overlayspec):
501 groups = overlayspec.split(',')
503 group = group.strip()
504 if group.find('-')!=-1:
505 nums = group.split('-')
507 syntax_error('overlay specs must be of the form <(%d-%d)|(%d), ...>', overlayspec)
513 syntax_error('not an int, overlay specs must be of the form <(%d-%d)|(%d), ...>', overlayspec)
515 overlays.extend(list(range(start,stop+1)))
520 syntax_error('not an int, overlay specs must be of the form <(%d-%d)|(%d), ...>', overlayspec)
524 overlays = make_unique(overlays)
527 def expand_code_parse_simpleanimspec(animspec):
529 (esc_open, esc_close) = expand_code_search_escape_sequences(animspec)
530 animspec = animspec.replace('\\[', esc_open)
531 animspec = animspec.replace('\\]', esc_close)
533 p = re.compile(r'^\[<([0-9,\-]+)>((?:.|\s)*)\]$')
534 m = p.match(animspec)
536 overlays = expand_code_parse_overlayspec(m.group(1))
539 syntax_error('specification does not match [<%d>%s]', animspec)
542 code = code.replace(esc_open, '[').replace(esc_close, ']')
544 return [(overlay, code) for overlay in overlays]
547 def expand_code_parse_animspec(animspec):
548 if len(animspec)<4 or not animspec.startswith('[['):
549 return ('simple', expand_code_parse_simpleanimspec(animspec))
552 (esc_open, esc_close) = expand_code_search_escape_sequences(animspec)
553 animspec = animspec.replace('\\[', esc_open)
554 animspec = animspec.replace('\\]', esc_close)
556 p = re.compile(r'\[|\]\[|\]')
557 simple_specs = ['[%s]'%s for s in [s for s in p.split(animspec) if len(s.strip())>0]]
560 simple_specs = [s.replace(esc_open, '\\[').replace(esc_close, '\\]') for s in simple_specs]
561 parsed_simple_specs = list(map(expand_code_parse_simpleanimspec, simple_specs))
563 for pss in parsed_simple_specs:
564 unified_pss.extend(pss)
565 return ('double', unified_pss)
568 def expand_code_getmaxoverlay(parsed_anims):
570 for anim in parsed_anims:
572 if spec[0] > max_overlay:
573 max_overlay = spec[0]
576 def expand_code_getminoverlay(parsed_anims):
577 min_overlay = sys.maxsize
578 for anim in parsed_anims:
580 if spec[0] < min_overlay:
581 min_overlay = spec[0]
582 if min_overlay == sys.maxsize:
587 def expand_code_genanims(parsed_animspec, minoverlay, maxoverlay, type):
588 #get maximum length of code
591 for simple_animspec in parsed_animspec:
592 if maxlen < len(simple_animspec[1]):
593 maxlen = len(simple_animspec[1])
596 fill = ''.join([' ' for i in range(0, maxlen)])
597 for x in range(minoverlay,maxoverlay+1):
600 for simple_animspec in parsed_animspec:
601 out[simple_animspec[0]-minoverlay] = simple_animspec[1]
605 def expand_code_getname(code):
606 hex2alpha_table = { '0':'a', '1':'b', '2':'c', '3':'d', \
607 '4':'e', '5':'f', '6':'g', '7':'h', '8':'i', '9':'j', \
608 'a':'k', 'b':'l', 'c':'m', 'd':'n', 'e':'o', 'f':'p' \
610 hexhash = md5hex(code)
611 alphahash = ''.join(hex2alpha_table[x] for x in hexhash)
614 def expand_code_makeoverprint(names, minoverlay):
615 out = ['\\begin{overprint}\n']
616 for (index, name) in enumerate(names):
617 out.append(' \\onslide<%d>\\%s\n' % (index+minoverlay, name))
618 out.append('\\end{overprint}\n')
622 def expand_code_get_unique_name(defverbs, code, lstparams):
623 """generate a collision free entry in the defverbs-map and names-list"""
624 name = expand_code_getname(code)
625 expanded_code = expand_code_make_defverb(expand_code_make_lstlisting(code, lstparams), name)
627 while name in defverbs and defverbs[name] != expanded_code:
628 rehash += chr(random.randint(65,90)) #append a character from A-Z to rehash value
629 name = expand_code_getname(code + rehash)
630 expanded_code = expand_code_make_defverb(expand_code_make_lstlisting(code, lstparams), name)
632 return (name, expanded_code)
634 def make_sorted(seq):
635 '''replacement for sorted built-in'''
640 def expand_code_segment(result, codebuffer, state):
641 #treat first line as params for lstlistings
642 lstparams = codebuffer[0]
645 #join lines into one string
646 code = ''.join(codebuffer)
648 #tokenize code into anim and non_anim parts
649 (anim, non_anim) = expand_code_tokenize_anims(code)
651 #generate multiple versions of the anim parts
652 parsed_anims = list(map(expand_code_parse_animspec, anim))
653 max_overlay = expand_code_getmaxoverlay(x[1] for x in parsed_anims)
654 #if there is unanimated code, use 0 as the starting overlay
655 if len(list(non_anim))>0:
658 min_overlay = expand_code_getminoverlay(x[1] for x in parsed_anims)
659 gen_anims = [expand_code_genanims(x[1], min_overlay, max_overlay, x[0]) for x in parsed_anims]
661 for i in range(0,max_overlay-min_overlay+1):
662 anim_map[i+min_overlay] = [x[i] for x in gen_anims]
665 for overlay in make_sorted(anim_map.keys()):
666 #combine non_anim and anim parts
667 anim_map[overlay].append('')
668 zipped = zip(non_anim, anim_map[overlay])
669 code = ''.join(x[0] + x[1] for x in zipped)
671 #generate a collision free entry in the defverbs-map and names-list
672 (name, expanded_code) = expand_code_get_unique_name(state.defverbs, code, lstparams)
674 #now we have a collision free entry, append it
676 state.defverbs[name] = expanded_code
678 #append overprint area to result
679 overprint = expand_code_makeoverprint(names, min_overlay)
680 result.append(overprint)
682 #we have no animations and can just put the defverbatim in
684 code = code.replace('\\[', '[').replace('\\]', ']')
685 (name, expanded_code) = expand_code_get_unique_name(state.defverbs, code, lstparams)
686 state.defverbs[name] = expanded_code
687 result.append('\n\\%s\n' % name)
691 def expand_code_defverbs(result, state):
692 result[state.code_pos] = result[state.code_pos] + '\n'.join(list(state.defverbs.values())) + '\n'
693 state.defverbs.clear()
695 def get_autotemplate_closing():
696 return '\n\end{document}\n'
698 def parse_bool(string):
701 if string == 'True' or string == 'true' or string == '1':
703 elif string == 'False' or string == 'false' or string =='0':
706 syntax_error('Boolean expected (True/true/1 or False/false/0)', string)
710 def parse_autotemplate(autotemplatebuffer):
712 @param autotemplatebuffer (list)
713 a list of lines found in the autotemplate section
715 a list of tuples of the form (string, string) with \command.parameters pairs
719 for line in autotemplatebuffer:
720 if len(line.lstrip())==0: #ignore empty lines
722 if len(line.lstrip())>0 and line.lstrip().startswith('%'): #ignore lines starting with % as comments
725 tokens = line.split('=', 1)
727 syntax_error('lines in the autotemplate section have to be of the form key=value', line)
729 autotemplate.append((tokens[0], tokens[1]))
733 def parse_usepackage(usepackage):
735 @param usepackage (str)
736 the unparsed usepackage string in the form [options]{name}
738 (name(str), options(str))
741 p = re.compile(r'^\s*(\[.*\])?\s*\{(.*)\}\s*$')
742 m = p.match(usepackage)
744 if len(g)<2 or len(g)>2:
745 syntax_error('usepackage specifications have to be of the form [%s]{%s}', usepackage)
746 elif g[1]==None and g[1].strip()!='':
747 syntax_error('usepackage specifications have to be of the form [%s]{%s}', usepackage)
751 return (name, options)
754 def unify_autotemplates(autotemplates):
755 usepackages = {} #packagename : options
760 for template in autotemplates:
761 for command in template:
762 if command[0] == 'usepackage':
763 (name, options) = parse_usepackage(command[1])
764 usepackages[name] = options
765 elif command[0] == 'titleframe':
766 titleframe = command[1]
767 elif command[0] == 'documentclass':
768 documentclass = command[1]
770 merged.append(command)
773 autotemplate.append(('documentclass', documentclass))
774 for (name, options) in usepackages.items():
775 if options is not None and options.strip() != '':
776 string = '%s{%s}' % (options, name)
778 string = '{%s}' % name
779 autotemplate.append(('usepackage', string))
780 autotemplate.append(('titleframe', titleframe))
782 autotemplate.extend(merged)
786 def expand_autotemplate_gen_opening(autotemplate):
788 @param autotemplate (list)
789 the specification of the autotemplate in the form of a list of tuples
791 the string the with generated latex code
795 for item in autotemplate:
796 if item[0]!='titleframe':
797 out.append('\\%s%s' % item)
799 titleframe = parse_bool(item[1])
801 out.append('\n\\begin{document}\n')
803 out.append('\n\\frame{\\titlepage}\n')
805 return '\n'.join(out)
808 def expand_autotemplate_opening(result, templatebuffer, state):
809 my_autotemplate = parse_autotemplate(templatebuffer)
810 the_autotemplate = unify_autotemplates([autotemplate, my_autotemplate])
812 opening = expand_autotemplate_gen_opening(the_autotemplate)
814 result.append(opening)
816 state.code_pos = len(result)
817 state.autotemplate_opened = True
820 def get_autotemplatemode(line, autotemplatemode):
821 autotemplatestart = re.compile(r'^<\[\s*autotemplate\s*\]')
822 autotemplateend = re.compile(r'^\[\s*autotemplate\s*\]>')
823 if not autotemplatemode and autotemplatestart.match(line)!=None:
824 line = autotemplatestart.sub('', line)
826 elif autotemplatemode and autotemplateend.match(line)!=None:
827 line = autotemplateend.sub('', line)
830 return (line, autotemplatemode)
832 def get_nowikimode(line, nowikimode):
834 if not nowikimode and nowikistartre.match(line)!=None:
835 line = nowikistartre.sub('', line)
837 elif nowikimode and nowikiendre.match(line)!=None:
838 line = nowikiendre.sub('', line)
841 return (line, nowikimode)
843 def get_codemode(line, codemode):
844 if not codemode and codestartre.match(line)!=None:
845 line = codestartre.sub('', line)
847 elif codemode and codeendre.match(line)!=None:
848 line = codeendre.sub('', line)
851 return (line, codemode)
853 def joinLines(lines):
854 """ join lines ending with unescaped percent signs, unless inside codemode or nowiki mode """
857 r = [] # result array
860 (_,nowikimode) = get_nowikimode(_l, nowikimode)
862 (_,codemode) = get_codemode(_l, codemode)
869 if not (nowikimode or codemode) and (len(l) > 1) and (l[-1] == "%") and (l[-2] != "\\"):
871 elif not (nowikimode or codemode) and (len(l) == 1) and (l[-1] == "%"):
880 def read_file_to_lines(filename):
883 f = open(filename, "r")
884 lines = joinLines(f.readlines())
887 pprint("Cannot read file: %s" % filename, sys.stderr)
893 def scan_for_selected_frames(lines):
894 """scans for frames that should be rendered exclusively, returns true if such frames have been found"""
895 p = re.compile("^!====\s*(.*?)\s*====(.*)", re.VERBOSE)
902 def line_opens_unselected_frame(line):
903 p = re.compile("^====\s*(.*?)\s*====(.*)", re.VERBOSE)
904 if p.match(line) is not None:
908 def line_opens_selected_frame(line):
909 p = re.compile("^!====\s*(.*?)\s*====(.*)", re.VERBOSE)
910 if p.match(line) is not None:
914 def line_closes_frame(line):
915 p = re.compile("^\s*\[\s*frame\s*\]>", re.VERBOSE)
916 if p.match(line) is not None:
920 def filter_selected_lines(lines):
923 selected_frame_opened = False
925 frame_manually_closed = False
927 if line_opens_selected_frame(line):
928 selected_frame_opened = True
931 if line_opens_unselected_frame(line):
932 selected_frame_opened = False
935 if line_closes_frame(line):
936 selected_frame_opened = False
938 frame_manually_closed = True
940 if selected_frame_opened or (frame_closed and not frame_manually_closed):
941 selected_lines.append(line)
943 return selected_lines
945 def convert2beamer(lines):
947 selectedframemode = scan_for_selected_frames(lines)
948 if selectedframemode:
949 out = convert2beamer_selected(lines)
951 out = convert2beamer_full(lines)
955 def convert2beamer_selected(lines):
956 selected_lines = filter_selected_lines(lines)
957 out = convert2beamer_full(selected_lines)
960 def include_file(line):
961 """ Extract filename to include.
964 a line that might include an inclusion
965 @return string or None
966 if the line contains an inclusion, return the filename,
967 otherwise return None
969 p = re.compile("\>\>\>(.*?)\<\<\<", re.VERBOSE)
971 filename = p.sub(r"\1", line)
976 def include_file_recursive(base):
983 for line in get_lines_from_cache(file_):
984 if nowikimode or codemode:
985 if nowikiendre.match(line):
987 elif codeendre.match(line):
990 elif nowikistartre.match(line):
993 elif codestartre.match(line):
997 include = include_file(line)
998 if include is not None:
1000 raise IncludeLoopException('Loop detected while trying '
1001 "to include: '%s'.\n" % include +
1002 'Stack: '+ "->".join(stack))
1011 def convert2beamer_full(lines):
1012 """ convert to LaTeX beamer"""
1014 result = [''] #start with one empty line as line 0
1016 autotemplatebuffer = []
1020 autotemplatemode = False
1023 (line, nowikimode) = get_nowikimode(line, nowikimode)
1027 (line, _codemode) = get_codemode(line, codemode)
1028 if _codemode and not codemode: #code mode was turned on
1030 elif not _codemode and codemode: #code mode was turned off
1031 expand_code_segment(result, codebuffer, state)
1032 codemode = _codemode
1035 codebuffer.append(line)
1037 (line, _autotemplatemode) = get_autotemplatemode(line, autotemplatemode)
1038 if _autotemplatemode and not autotemplatemode: #autotemplate mode was turned on
1039 autotemplatebuffer = []
1040 elif not _autotemplatemode and autotemplatemode: #autotemplate mode was turned off
1041 expand_autotemplate_opening(result, autotemplatebuffer, state)
1042 autotemplatemode = _autotemplatemode
1044 if autotemplatemode:
1045 autotemplatebuffer.append(line)
1047 state.current_line = len(result)
1048 result.append(transform(line, state))
1050 result.append(transform("", state)) # close open environments
1052 if state.frame_opened:
1053 result.append(get_frame_closing(state))
1054 if state.autotemplate_opened:
1055 result.append(get_autotemplate_closing())
1057 #insert defverbs somewhere at the beginning
1058 expand_code_defverbs(result, state)
1062 def print_result(lines):
1063 """ print result to stdout """
1065 pprint(l, file=sys.stdout)
1068 def redirect_stdout(outfilename):
1069 global _redirected_stdout
1070 outfile = open(outfilename, "wt")
1071 _redirected_stdout = outfile
1074 """ check parameters, start file processing """
1075 usage = "%prog [options] [input1.txt [input2.txt ...]] > output.tex"
1076 version = "%prog (http://wiki2beamer.sf.net), version: " + VERSIONTAG
1078 parser = optparse.OptionParser(usage="\n " + usage, version=version)
1079 parser.add_option("-o", "--output", dest="output", metavar="FILE", help="write output to FILE instead of stdout")
1080 opts, args = parser.parse_args()
1082 if opts.output is not None:
1083 redirect_stdout(opts.output)
1086 if not sys.stdin.isatty():
1087 _file_cache['stdin'] = joinLines(sys.stdin.readlines())
1088 input_files.append('stdin')
1089 elif len(args) == 0:
1090 parser.error("You supplied no files to convert!")
1094 for file_ in input_files:
1095 lines += include_file_recursive(file_)
1097 lines = convert2beamer(lines)
1101 if __name__ == "__main__":