1 # -*- test-case-name: twisted.lore.test.test_slides -*-
2 # Copyright (c) Twisted Matrix Laboratories.
3 # See LICENSE for details.
6 Rudimentary slide support for Lore.
9 - Complete mgp output target
13 - Add HTML output targets
14 - one slides per page (with navigation links)
20 <head><title>Title of talk</title></head>
23 <h1>Title of talk</h1>
29 <li>Look ma, I'm <strong>bold</strong>!</li>
46 from xml.dom import minidom as dom
48 from cStringIO import StringIO
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
60 hacked_entities = { 'amp': ' &', 'gt': ' >', 'lt': ' <', 'quot': ' "',
63 entities = { 'amp': '&', 'gt': '>', 'lt': '<', 'quot': '"',
66 class MagicpointOutput(BaseLatexSpitter):
69 def writeNodeData(self, node):
71 getLatexText(node, buf.write, entities=hacked_entities)
72 data = buf.getvalue().rstrip().replace('\n', ' ')
73 self.writer(re.sub(' +', ' ', data))
75 def visitNode_title(self, node):
76 self.title = domhelpers.getNodeText(node)
78 def visitNode_body(self, node):
79 # Adapted from tree.generateToC
80 self.fontStack = [('standard', None)]
83 self.writer(self.start_h2)
84 self.writer(self.title)
85 self.writer(self.end_h2)
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)
93 self.writer(self.start_h2)
94 self.writer(self.title)
95 self.writer(self.end_h2)
97 for element in getHeaders(node):
98 level = int(element.tagName[1])-1
99 self.writer(level * '\t')
100 self.writer(domhelpers.getNodeText(element))
103 self.visitNodeDefault(node)
105 def visitNode_div_author(self, node):
106 # Skip this node; it's already been used by visitNode_body
109 def visitNode_div_pause(self, node):
110 self.writer('%pause\n')
112 def visitNode_pre(self, node):
113 # TODO: Syntax highlighting
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')
122 self.writer(' ' + line + '\n')
123 del self.fontStack[-1]
124 self.writer('%' + self.fontName() + '\n')
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
135 def visitNode_strong(self, node):
136 self.doFont(node, 'bold')
138 def visitNode_em(self, node):
139 self.doFont(node, 'italic')
141 def visitNode_code(self, node):
142 self.doFont(node, 'typewriter')
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')
152 names = [x[0] for x in self.fontStack]
153 if 'typewriter' in names:
160 if 'italic' in names:
166 sizes = [x[1] for x in self.fontStack]
170 return 'font "%s", size %d' % (name, size)
172 return 'font "%s"' % name
174 start_h2 = "%page\n\n"
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)
188 processFile(spitter, fin)
193 # HTML DOM tree stuff
195 def splitIntoSlides(document):
196 body = domhelpers.findNodesNamed(document, 'body')[0]
200 for child in body.childNodes:
201 if isinstance(child, dom.Element) and child.tagName == 'h2':
203 slides.append((title, slide))
205 title = domhelpers.getNodeText(child)
208 slides.append((title, slide))
211 def insertPrevNextLinks(slides, filename, ext):
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))
222 text.data = slides[slide.pos+offset].title
223 node.appendChild(text)
225 for node in domhelpers.findElementsWithAttribute(slide.dom, "class", name):
227 for child in node.parentNode.childNodes:
229 del node.parentNode.childNodes[pos]
235 def __init__(self, dom, title, pos):
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)
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)
253 # Insert the slides into the template
256 for title, slide in splitIntoSlides(document):
257 t = template.cloneNode(1)
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))
270 insertPrevNextLinks(slides, os.path.splitext(os.path.basename(fullpath)), ext)
274 from tree import makeSureDirectoryExists
276 def getOutputFileName(originalFileName, outputExtension, index):
277 return os.path.splitext(originalFileName)[0]+'-'+str(index) + outputExtension
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)
292 class ProsperSlides(LatexSpitter):
294 start_html = '\\documentclass[ps]{prosper}\n'
295 start_body = '\\begin{document}\n'
296 start_div_author = '\\author{'
299 def visitNode_h2(self, node):
302 self.end_body = '\\end{slide}\n\n' + self.end_body
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)
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)
315 class PagebreakLatex(LatexSpitter):
321 start_html = LatexSpitter.start_html+"\\date{}\n"
322 start_body = '\\begin{document}\n\n'
324 def visitNode_h2(self, node):
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)
339 class TwoPagebreakLatex(PagebreakLatex):
344 class SlidesProcessingFunctionFactory(default.ProcessingFunctionFactory):
346 latexSpitters = default.ProcessingFunctionFactory.latexSpitters.copy()
347 latexSpitters['prosper'] = ProsperSlides
348 latexSpitters['page'] = PagebreakLatex
349 latexSpitters['twopage'] = TwoPagebreakLatex
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")
359 factory=SlidesProcessingFunctionFactory()