# Twisted, the Framework of Your Internet
# Copyright (C) 2001-2002 Matthew W. Lefkowitz
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of version 2.1 of the GNU Lesser General Public
# License as published by the Free Software Foundation.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
"""Rudimentary slide support for Lore.
TODO:
- Complete mgp output target
- syntax highlighting
- saner font handling
- probably lots more
- Add HTML output targets
- one slides per page (with navigation links)
- all in one page
Example input file::
<html>
<head><title>Title of talk</title></head>
<body>
<h1>Title of talk</h1>
<h2>First Slide</h2>
<ul>
<li>Bullet point</li>
<li>Look ma, I'm <strong>bold</strong>!</li>
<li>... etc ...</li>
</ul>
<h2>Second Slide</h2>
<pre class="python">
# Sample code sample.
print "Hello, World!"
</pre>
</body>
</html>
"""
from __future__ import nested_scopes
from twisted.lore import default
from twisted.web import domhelpers, microdom
from twisted.python import text
# These should be factored out
from twisted.lore.latex import BaseLatexSpitter, LatexSpitter, processFile, \
getLatexText, HeadingLatexSpitter
from twisted.lore.tree import getHeaders
from tree import removeH1, fixAPI, fontifyPython, \
addPyListings, addHTMLListings, setTitle
import os, os.path, re
from cStringIO import StringIO
hacked_entities = { 'amp': ' &', 'gt': ' >', 'lt': ' <', 'quot': ' "',
'copy': ' (c)'}
entities = { 'amp': '&', 'gt': '>', 'lt': '<', 'quot': '"',
'copy': '(c)'}
class MagicpointOutput(BaseLatexSpitter):
bulletDepth = 0
def writeNodeData(self, node):
buf = StringIO()
getLatexText(node, buf.write, entities=hacked_entities)
data = buf.getvalue().rstrip().replace('\n', ' ')
self.writer(re.sub(' +', ' ', data))
def visitNode_title(self, node):
self.title = domhelpers.getNodeText(node)
def visitNode_body(self, node):
# Adapted from tree.generateToC
self.fontStack = [('standard', None)]
# Title slide
self.writer(self.start_h2)
self.writer(self.title)
self.writer(self.end_h2)
self.writer('%center\n\n\n\n\n')
for authorNode in domhelpers.findElementsWithAttribute(node, 'class', 'author'):
getLatexText(authorNode, self.writer, entities=entities)
self.writer('\n')
# Table of contents
self.writer(self.start_h2)
self.writer(self.title)
self.writer(self.end_h2)
for element in getHeaders(node):
level = int(element.tagName[1])-1
self.writer(level * '\t')
self.writer(domhelpers.getNodeText(element))
self.writer('\n')
self.visitNodeDefault(node)
def visitNode_div_author(self, node):
# Skip this node; it's already been used by visitNode_body
pass
def visitNode_div_pause(self, node):
self.writer('%pause\n')
def visitNode_pre(self, node):
# TODO: Syntax highlighting
buf = StringIO()
getLatexText(node, buf.write, entities=entities)
data = buf.getvalue()
data = text.removeLeadingTrailingBlanks(data)
lines = data.split('\n')
self.fontStack.append(('typewriter', 4))
self.writer('%' + self.fontName() + '\n')
for line in lines:
self.writer(' ' + line + '\n')
del self.fontStack[-1]
self.writer('%' + self.fontName() + '\n')
def visitNode_ul(self, node):
if self.bulletDepth > 0:
self.writer(self._start_ul)
self.bulletDepth += 1
self.start_li = self._start_li * self.bulletDepth
self.visitNodeDefault(node)
self.bulletDepth -= 1
self.start_li = self._start_li * self.bulletDepth
def visitNode_strong(self, node):
self.doFont(node, 'bold')
def visitNode_em(self, node):
self.doFont(node, 'italic')
def visitNode_code(self, node):
self.doFont(node, 'typewriter')
def doFont(self, node, style):
self.fontStack.append((style, None))
self.writer(' \n%cont, ' + self.fontName() + '\n')
self.visitNodeDefault(node)
del self.fontStack[-1]
self.writer('\n%cont, ' + self.fontName() + '\n')
def fontName(self):
names = [x[0] for x in self.fontStack]
if 'typewriter' in names:
name = 'typewriter'
else:
name = ''
if 'bold' in names:
name += 'bold'
if 'italic' in names:
name += 'italic'
if name == '':
name = 'standard'
sizes = [x[1] for x in self.fontStack]
sizes.reverse()
for size in sizes:
if size:
return 'font "%s", size %d' % (name, size)
return 'font "%s"' % name
start_h2 = "%page\n\n"
end_h2 = '\n\n\n'
_start_ul = '\n'
_start_li = "\t"
end_li = "\n"
def convertFile(filename, outputter, template, ext=".mgp"):
fout = open(os.path.splitext(filename)[0]+ext, 'w')
fout.write(open(template).read())
spitter = outputter(fout.write, os.path.dirname(filename), filename)
fin = open(filename)
processFile(spitter, fin)
fin.close()
fout.close()
# HTML DOM tree stuff
def splitIntoSlides(document):
body = domhelpers.findNodesNamed(document, 'body')[0]
slides = []
slide = []
title = '(unset)'
for child in body.childNodes:
if isinstance(child, microdom.Element) and child.tagName == 'h2':
if slide:
slides.append((title, slide))
slide = []
title = domhelpers.getNodeText(child)
else:
slide.append(child)
slides.append((title, slide))
return slides
def insertPrevNextLinks(slides, filename, ext):
for slide in slides:
for name, offset in (("previous", -1), ("next", +1)):
if (slide.pos > 0 and name == "previous") or \
(slide.pos < len(slides)-1 and name == "next"):
for node in domhelpers.findElementsWithAttribute(slide.dom, "class", name):
if node.tagName == 'a':
node.setAttribute('href', '%s-%d%s'
% (filename[0], slide.pos+offset, ext))
else:
node.appendChild(microdom.Text(slides[slide.pos+offset].title))
else:
for node in domhelpers.findElementsWithAttribute(slide.dom, "class", name):
pos = 0
for child in node.parentNode.childNodes:
if child is node:
del node.parentNode.childNodes[pos]
break
pos += 1
class HTMLSlide:
def __init__(self, dom, title, pos):
self.dom = dom
self.title = title
self.pos = pos
def munge(document, template, linkrel, d, fullpath, ext, url, config):
# FIXME: This has *way* to much duplicated crap in common with tree.munge
#fixRelativeLinks(template, linkrel)
removeH1(document)
fixAPI(document, url)
fontifyPython(document)
addPyListings(document, d)
addHTMLListings(document, d)
#fixLinks(document, ext)
#putInToC(template, generateToC(document))
template = template.cloneNode(1)
# Insert the slides into the template
slides = []
pos = 0
for title, slide in splitIntoSlides(document):
t = template.cloneNode(1)
setTitle(t, [microdom.Text(title)])
tmplbody = domhelpers.findElementsWithAttribute(t, "class", "body")[0]
tmplbody.childNodes = slide
tmplbody.setAttribute("class", "content")
# FIXME: Next/Prev links
# FIXME: Perhaps there should be a "Template" class? (setTitle/setBody
# could be methods...)
slides.append(HTMLSlide(t, title, pos))
pos += 1
insertPrevNextLinks(slides, os.path.splitext(os.path.basename(fullpath)), ext)
return slides
from tree import makeSureDirectoryExists
def getOutputFileName(originalFileName, outputExtension, index):
return os.path.splitext(originalFileName)[0]+'-'+str(index) + outputExtension
def doFile(filename, linkrel, ext, url, templ, options={}, outfileGenerator=getOutputFileName):
from tree import parseFileAndReport
doc = parseFileAndReport(filename)
slides = munge(doc, templ, linkrel, os.path.dirname(filename), filename, ext, url, options)
for slide, index in zip(slides, range(len(slides))):
newFilename = outfileGenerator(filename, ext, index)
makeSureDirectoryExists(newFilename)
slide.dom.writexml(open(newFilename, 'wb'))
# Prosper output
class ProsperSlides(LatexSpitter):
firstSlide = 1
start_html = '\\documentclass[ps]{prosper}\n'
start_body = '\\begin{document}\n'
start_div_author = '\\author{'
end_div_author = '}'
def visitNode_h2(self, node):
if self.firstSlide:
self.firstSlide = 0
self.end_body = '\\end{slide}\n\n' + self.end_body
else:
self.writer('\\end{slide}\n\n')
self.writer('\\begin{slide}{')
spitter = HeadingLatexSpitter(self.writer, self.currDir, self.filename)
spitter.visitNodeDefault(node)
self.writer('}')
def _write_img(self, target):
self.writer('\\begin{center}\\includegraphics[%%\nwidth=1.0\n\\textwidth,'
'height=1.0\\textheight,\nkeepaspectratio]{%s}\\end{center}\n' % target)
class PagebreakLatex(LatexSpitter):
everyN = 1
currentN = 0
seenH2 = 0
start_html = LatexSpitter.start_html+"\\date{}\n"
start_body = '\\begin{document}\n\n'
def visitNode_h2(self, node):
if not self.seenH2:
self.currentN = 0
self.seenH2 = 1
else:
self.currentN += 1
self.currentN %= self.everyN
if not self.currentN:
self.writer('\\clearpage\n')
level = (int(node.tagName[1])-2)+self.baseLevel
self.writer('\n\n\\'+level*'sub'+'section*{')
spitter = HeadingLatexSpitter(self.writer, self.currDir, self.filename)
spitter.visitNodeDefault(node)
self.writer('}\n')
class TwoPagebreakLatex(PagebreakLatex):
everyN = 2
class SlidesProcessingFunctionFactory(default.ProcessingFunctionFactory):
latexSpitters = default.ProcessingFunctionFactory.latexSpitters.copy()
latexSpitters['prosper'] = ProsperSlides
latexSpitters['page'] = PagebreakLatex
latexSpitters['twopage'] = TwoPagebreakLatex
def getDoFile(self):
return doFile
def generate_mgp(self, d, fileNameGenerator=None):
template = d.get('template', 'template.mgp')
df = lambda file, linkrel: convertFile(file, MagicpointOutput, template, ext=".mgp")
return df
factory=SlidesProcessingFunctionFactory()
syntax highlighted by Code2HTML, v. 0.9.1