# $Id$ # simple markdown to S5 translator (in progress) import site # for exemaker import os, re, sys import htmlentitydefs import markdown import zipfile import shutil try: import xml.etree.cElementTree as ET except ImportError: try: import cElementTree as ET except ImportError: import elementtree.ElementTree as ET def gettext(elem): text = elem.text or "" for e in elem: text += gettext(e) if e.tail: text += e.tail return text ## # Unpacks a ZIP file. def zip_explode(filename, filter): zip = zipfile.ZipFile(filename) for name in zip.namelist(): outfile = filter(name) if not outfile: continue dirname = os.path.dirname(outfile) if not os.path.isdir(dirname): os.makedirs(dirname) data = zip.read(name) if not data: continue # ? # print name, "->", outfile f = open(outfile, "wb") f.write(data) f.close() ## # Slideshow wrapper. class SlideShow: def __init__(self, directory): self.directory = directory ## # Prepares a slideshow directory, using data from an S5 template ZIP. def init(self, template_zip="s5-blank.zip", theme_zip=None): def filter(zipname): # get rid of mac crud if zipname[:8] == "__MACOSX": return None if os.path.basename(zipname) == ".DS_Store": return None parts = zipname.split("s5-blank/", 1) assert len(parts) == 2 and not parts[0], "unexpected zip contents" return os.path.join(self.directory, parts[1]) zip_explode(template_zip, filter) def filter(zipname): # FIXME: it would of course be nice if the standard styles # could just be dropped on top of the ui directory, or be # entirely self-contained, but they seem to be somewhere in # between. for now, fake things by stuffing all the files # inside ui/default if zipname.endswith(".js"): return None basename = os.path.basename(zipname) return os.path.join(self.directory, "ui", "default", basename) if theme_zip: zip_explode(theme_zip, filter) ## # Loads and prepares the S5 slideshow template. def load_template(self): filename = os.path.join(self.directory, "s5-blank.html") self.slideshow = ET.parse(filename) # clean up namespace NS_XHTML = "{http://www.w3.org/1999/xhtml}" for elem in self.slideshow.getiterator(): if elem.tag.startswith(NS_XHTML): elem.tag = elem.tag[len(NS_XHTML):] # locate template elements self.elem_title = self.elem_footer = self.elem_presentation = None for elem in self.slideshow.getiterator(): if elem.tag == "title": self.elem_title = elem elem.text = self.directory if elem.tag == "div": if elem.get("id") == "footer": self.elem_footer = elem del elem[:] elif elem.get("class") == "presentation": self.elem_presentation = elem del elem[:] ## # Sets the slideshow title. def set_title(self, title): self.elem_title.text = title ## # Sets the slideshow footer. def set_footer(self, location, title): del self.elem_footer[:] ET.SubElement(self.elem_footer, "h1").text = location ET.SubElement(self.elem_footer, "h2").text = title ## # Adds a slide to the slideshow. def add_slide(self, slide, handout=None): div = ET.SubElement(self.elem_presentation, "div") div.set("class", "slide") div[:] = slide[:] if handout: div.append(handout) ## # Writes the slideshow to disk. def save(self, filename=None): # fixup the element tree special_div_ids = ("controls", "currentSlide", "header") for elem in self.slideshow.getiterator(): # handle INCREMENTAL lists if elem.tag in ("ul", "ol") and len(elem): if gettext(elem[0]).strip() == "INCREMENTAL": del elem[0] elem.set("class", "incremental") # some empty elements cannot use syntax. force # syntax by adding a space to those elements. if (elem.tag == "div" and elem.get("id") in special_div_ids or elem.tag == "script"): elem.text = elem.text or " " # generate the output file # there's no simple way to make ET output the doctype header and # use a default namespace, so we have to do that outselves. filename = os.path.join(self.directory, filename or "index.html") out = open(filename, "w") print >>out, """ """.strip() for elem in self.slideshow.getroot(): ET.ElementTree(elem).write(out) print >>out, "" out.close() print out.name, "ok" def make_slideshow(filename, theme_zip=None): name = os.path.splitext(os.path.basename(filename))[0] text = open(filename).read() try: text = unicode(text, "utf-8") except UnicodeError: text = unicode(text, "iso-8859-1") def fixup(m): try: return unichr(htmlentitydefs.name2codepoint[m.group(1)]) except KeyError: return "&" + m.group(0) # ? text = re.sub("&(\w+);", fixup, text) text = text.encode("ascii", "xmlcharrefreplace") slideshow_dir = name + ".s5" slideshow = SlideShow(slideshow_dir) slideshow.init(theme_zip=theme_zip) # do we need tidy here? content = ET.XML("" + markdown.markdown(text) + "") slideshow.load_template() # look for variable block if content and content[0].tag == "p": elem = content[0] if elem.text and elem.text[0] == "$": del content[0] title = footer1 = footer2 = None for line in elem.text.split("\n"): line = line.strip() if line.startswith("$TITLE"): title = line[6:] elif line.startswith("$FOOTER1"): footer1 = line[8:] elif line.startswith("$FOOTER2"): footer2 = line[8:] elif line: print "---", "unknown variable:", line if title: slideshow.set_title(title) if footer1 or footer2: slideshow.set_footer(footer1, footer2) images = {} for elem in content.findall(".//img"): src = elem.get("src") if src: basename = os.path.basename(src) images[os.path.join(os.path.dirname(filename), src)] = basename elem.set("src", basename) # split on H1 tags slide = ET.Element("div") for elem in content: if elem.tag == "h1": if slide: slideshow.add_slide(slide) slide = ET.Element("div") slide.append(elem) if slide: slideshow.add_slide(slide) slideshow.save() # copy images for source, target in images.items(): target = os.path.join(slideshow.directory, target) shutil.copy(source, target) print target, "ok" if __name__ == "__main__": try: file = sys.argv[1] except IndexError: print "usage: s5make.py input.txt [theme.zip]" sys.exit() try: theme = sys.argv[2] except IndexError: theme = None make_slideshow(file, theme)