Initial import to Tizen
[profile/ivi/python-twisted.git] / twisted / lore / slides.py
1 # -*- test-case-name: twisted.lore.test.test_slides -*-
2 # Copyright (c) Twisted Matrix Laboratories.
3 # See LICENSE for details.
4
5 """
6 Rudimentary slide support for Lore.
7
8 TODO:
9     - Complete mgp output target
10         - syntax highlighting
11         - saner font handling
12         - probably lots more
13     - Add HTML output targets
14         - one slides per page (with navigation links)
15         - all in one page
16
17 Example input file::
18     <html>
19
20     <head><title>Title of talk</title></head>
21
22     <body>
23     <h1>Title of talk</h1>
24
25     <h2>First Slide</h2>
26
27     <ul>
28       <li>Bullet point</li>
29       <li>Look ma, I'm <strong>bold</strong>!</li>
30       <li>... etc ...</li>
31     </ul>
32
33
34     <h2>Second Slide</h2>
35
36     <pre class="python">
37     # Sample code sample.
38     print "Hello, World!"
39     </pre>
40
41     </body>
42
43     </html>
44 """
45
46 from xml.dom import minidom as dom
47 import os.path, re
48 from cStringIO import StringIO
49
50 from twisted.lore import default
51 from twisted.web import domhelpers
52 from twisted.python import text
53 # These should be factored out
54 from twisted.lore.latex import BaseLatexSpitter, LatexSpitter, processFile
55 from twisted.lore.latex import getLatexText, HeadingLatexSpitter
56 from twisted.lore.tree import getHeaders
57 from twisted.lore.tree import removeH1, fixAPI, fontifyPython
58 from twisted.lore.tree import addPyListings, addHTMLListings, setTitle
59
60 hacked_entities = { 'amp': ' &', 'gt': ' >', 'lt': ' <', 'quot': ' "',
61                     'copy': ' (c)'}
62
63 entities = { 'amp': '&', 'gt': '>', 'lt': '<', 'quot': '"',
64              'copy': '(c)'}
65
66 class MagicpointOutput(BaseLatexSpitter):
67     bulletDepth = 0
68
69     def writeNodeData(self, node):
70         buf = StringIO()
71         getLatexText(node, buf.write, entities=hacked_entities)
72         data = buf.getvalue().rstrip().replace('\n', ' ')
73         self.writer(re.sub(' +', ' ', data))
74
75     def visitNode_title(self, node):
76         self.title = domhelpers.getNodeText(node)
77
78     def visitNode_body(self, node):
79         # Adapted from tree.generateToC
80         self.fontStack = [('standard', None)]
81
82         # Title slide
83         self.writer(self.start_h2)
84         self.writer(self.title)
85         self.writer(self.end_h2)
86
87         self.writer('%center\n\n\n\n\n')
88         for authorNode in domhelpers.findElementsWithAttribute(node, 'class', 'author'):
89             getLatexText(authorNode, self.writer, entities=entities)
90             self.writer('\n')
91
92         # Table of contents
93         self.writer(self.start_h2)
94         self.writer(self.title)
95         self.writer(self.end_h2)
96
97         for element in getHeaders(node):
98             level = int(element.tagName[1])-1
99             self.writer(level * '\t')
100             self.writer(domhelpers.getNodeText(element))
101             self.writer('\n')
102
103         self.visitNodeDefault(node)
104
105     def visitNode_div_author(self, node):
106         # Skip this node; it's already been used by visitNode_body
107         pass
108
109     def visitNode_div_pause(self, node):
110         self.writer('%pause\n')
111
112     def visitNode_pre(self, node):
113         # TODO: Syntax highlighting
114         buf = StringIO()
115         getLatexText(node, buf.write, entities=entities)
116         data = buf.getvalue()
117         data = text.removeLeadingTrailingBlanks(data)
118         lines = data.split('\n')
119         self.fontStack.append(('typewriter', 4))
120         self.writer('%' + self.fontName() + '\n')
121         for line in lines:
122             self.writer(' ' + line + '\n')
123         del self.fontStack[-1]
124         self.writer('%' + self.fontName() + '\n')
125
126     def visitNode_ul(self, node):
127         if self.bulletDepth > 0:
128             self.writer(self._start_ul)
129         self.bulletDepth += 1
130         self.start_li = self._start_li * self.bulletDepth
131         self.visitNodeDefault(node)
132         self.bulletDepth -= 1
133         self.start_li = self._start_li * self.bulletDepth
134
135     def visitNode_strong(self, node):
136         self.doFont(node, 'bold')
137
138     def visitNode_em(self, node):
139         self.doFont(node, 'italic')
140
141     def visitNode_code(self, node):
142         self.doFont(node, 'typewriter')
143
144     def doFont(self, node, style):
145         self.fontStack.append((style, None))
146         self.writer(' \n%cont, ' + self.fontName() + '\n')
147         self.visitNodeDefault(node)
148         del self.fontStack[-1]
149         self.writer('\n%cont, ' + self.fontName() + '\n')
150
151     def fontName(self):
152         names = [x[0] for x in self.fontStack]
153         if 'typewriter' in names:
154             name = 'typewriter'
155         else:
156             name = ''
157
158         if 'bold' in names:
159             name += 'bold'
160         if 'italic' in names:
161             name += 'italic'
162
163         if name == '':
164             name = 'standard'
165
166         sizes = [x[1] for x in self.fontStack]
167         sizes.reverse()
168         for size in sizes:
169             if size:
170                 return 'font "%s", size %d' % (name, size)
171
172         return 'font "%s"' % name
173
174     start_h2 = "%page\n\n"
175     end_h2 = '\n\n\n'
176
177     _start_ul = '\n'
178
179     _start_li = "\t"
180     end_li = "\n"
181
182
183 def convertFile(filename, outputter, template, ext=".mgp"):
184     fout = open(os.path.splitext(filename)[0]+ext, 'w')
185     fout.write(open(template).read())
186     spitter = outputter(fout.write, os.path.dirname(filename), filename)
187     fin = open(filename)
188     processFile(spitter, fin)
189     fin.close()
190     fout.close()
191
192
193 # HTML DOM tree stuff
194
195 def splitIntoSlides(document):
196     body = domhelpers.findNodesNamed(document, 'body')[0]
197     slides = []
198     slide = []
199     title = '(unset)'
200     for child in body.childNodes:
201         if isinstance(child, dom.Element) and child.tagName == 'h2':
202             if slide:
203                 slides.append((title, slide))
204                 slide = []
205             title = domhelpers.getNodeText(child)
206         else:
207             slide.append(child)
208     slides.append((title, slide))
209     return slides
210
211 def insertPrevNextLinks(slides, filename, ext):
212     for slide in slides:
213         for name, offset in (("previous", -1), ("next", +1)):
214             if (slide.pos > 0 and name == "previous") or \
215                (slide.pos < len(slides)-1 and name == "next"):
216                 for node in domhelpers.findElementsWithAttribute(slide.dom, "class", name):
217                     if node.tagName == 'a':
218                         node.setAttribute('href', '%s-%d%s'
219                                           % (filename[0], slide.pos+offset, ext))
220                     else:
221                         text = dom.Text()
222                         text.data = slides[slide.pos+offset].title
223                         node.appendChild(text)
224             else:
225                 for node in domhelpers.findElementsWithAttribute(slide.dom, "class", name):
226                     pos = 0
227                     for child in node.parentNode.childNodes:
228                         if child is node:
229                             del node.parentNode.childNodes[pos]
230                             break
231                         pos += 1
232
233
234 class HTMLSlide:
235     def __init__(self, dom, title, pos):
236         self.dom = dom
237         self.title = title
238         self.pos = pos
239
240
241 def munge(document, template, linkrel, d, fullpath, ext, url, config):
242     # FIXME: This has *way* to much duplicated crap in common with tree.munge
243     #fixRelativeLinks(template, linkrel)
244     removeH1(document)
245     fixAPI(document, url)
246     fontifyPython(document)
247     addPyListings(document, d)
248     addHTMLListings(document, d)
249     #fixLinks(document, ext)
250     #putInToC(template, generateToC(document))
251     template = template.cloneNode(1)
252
253     # Insert the slides into the template
254     slides = []
255     pos = 0
256     for title, slide in splitIntoSlides(document):
257         t = template.cloneNode(1)
258         text = dom.Text()
259         text.data = title
260         setTitle(t, [text])
261         tmplbody = domhelpers.findElementsWithAttribute(t, "class", "body")[0]
262         tmplbody.childNodes = slide
263         tmplbody.setAttribute("class", "content")
264         # FIXME: Next/Prev links
265         # FIXME: Perhaps there should be a "Template" class?  (setTitle/setBody
266         #        could be methods...)
267         slides.append(HTMLSlide(t, title, pos))
268         pos += 1
269
270     insertPrevNextLinks(slides, os.path.splitext(os.path.basename(fullpath)), ext)
271
272     return slides
273
274 from tree import makeSureDirectoryExists
275
276 def getOutputFileName(originalFileName, outputExtension, index):
277     return os.path.splitext(originalFileName)[0]+'-'+str(index) + outputExtension
278
279 def doFile(filename, linkrel, ext, url, templ, options={}, outfileGenerator=getOutputFileName):    
280     from tree import parseFileAndReport
281     doc = parseFileAndReport(filename)
282     slides = munge(doc, templ, linkrel, os.path.dirname(filename), filename, ext, url, options)
283     for slide, index in zip(slides, range(len(slides))):
284         newFilename = outfileGenerator(filename, ext, index)
285         makeSureDirectoryExists(newFilename)
286         f = open(newFilename, 'wb')
287         slide.dom.writexml(f)
288         f.close()
289
290 # Prosper output
291
292 class ProsperSlides(LatexSpitter):
293     firstSlide = 1
294     start_html = '\\documentclass[ps]{prosper}\n'
295     start_body = '\\begin{document}\n'
296     start_div_author = '\\author{'
297     end_div_author = '}'
298
299     def visitNode_h2(self, node):
300         if self.firstSlide:
301             self.firstSlide = 0
302             self.end_body = '\\end{slide}\n\n' + self.end_body
303         else:
304             self.writer('\\end{slide}\n\n')
305         self.writer('\\begin{slide}{')
306         spitter = HeadingLatexSpitter(self.writer, self.currDir, self.filename)
307         spitter.visitNodeDefault(node)
308         self.writer('}')
309
310     def _write_img(self, target):
311         self.writer('\\begin{center}\\includegraphics[%%\nwidth=1.0\n\\textwidth,'
312                     'height=1.0\\textheight,\nkeepaspectratio]{%s}\\end{center}\n' % target)
313
314
315 class PagebreakLatex(LatexSpitter):
316
317     everyN = 1
318     currentN = 0
319     seenH2 = 0
320
321     start_html = LatexSpitter.start_html+"\\date{}\n"
322     start_body = '\\begin{document}\n\n'
323
324     def visitNode_h2(self, node):
325         if not self.seenH2:
326             self.currentN = 0
327             self.seenH2 = 1
328         else:
329             self.currentN += 1
330             self.currentN %= self.everyN
331             if not self.currentN:
332                 self.writer('\\clearpage\n')
333         level = (int(node.tagName[1])-2)+self.baseLevel
334         self.writer('\n\n\\'+level*'sub'+'section*{')
335         spitter = HeadingLatexSpitter(self.writer, self.currDir, self.filename)
336         spitter.visitNodeDefault(node)
337         self.writer('}\n')
338
339 class TwoPagebreakLatex(PagebreakLatex):
340
341     everyN = 2
342
343
344 class SlidesProcessingFunctionFactory(default.ProcessingFunctionFactory):
345
346     latexSpitters = default.ProcessingFunctionFactory.latexSpitters.copy()
347     latexSpitters['prosper'] = ProsperSlides
348     latexSpitters['page'] = PagebreakLatex
349     latexSpitters['twopage'] = TwoPagebreakLatex
350
351     def getDoFile(self):
352         return doFile
353
354     def generate_mgp(self, d, fileNameGenerator=None):
355         template = d.get('template', 'template.mgp')
356         df = lambda file, linkrel: convertFile(file, MagicpointOutput, template, ext=".mgp")
357         return df
358
359 factory=SlidesProcessingFunctionFactory()