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