#!/usr/bin/env python #================================================================ # litlxml: Extract code from literate-programming source files. # For documentation, see: # http://www.nmt.edu/tcc/help/lang/python/examples/litlxml/ #---------------------------------------------------------------- # Overall intended function: # [ output files named in input files given on the command line # := code fragments designated for those files # sys.stderr +:= error messages if any ] #---------------------------------------------------------------- import sys from lxml import etree #================================================================ # Manifest constants #---------------------------------------------------------------- PROG_ELT = "programlisting" ROLE_ATTR = "role" ROLE_PREFIX = "outFile:" #================================================================ # Verification functions #---------------------------------------------------------------- # lit-elt == an XML element whose GID is PROG_ELT, and which # has an attribute ROLE_ATTR whose value starts with # ROLE_PREFIX #---------------------------------------------------------------- # lit-dest(elt) == the part of the ROLE_ATTR value after # ROLE_PREFIX in a lit-elt #---------------------------------------------------------------- # lit-content(elt) == The text content of element (elt) and # any descendants #---------------------------------------------------------------- # - - - - - m a i n def main(): """Main program for litlxml.""" #-- 1 -- for inFileName in sys.argv[1:]: #-- 1 body -- # [ if inFileName names a readable, valid DocBook XML file -> # output files named in that file := code fragments # designated for those files # sys.stderr +:= error messages from processing that file, # if any # else -> # sys.stderr +:= error message ] processFile ( inFileName ) # - - - p r o c e s s F i l e def processFile ( fileName ): """Process one input file. [ inFileName is a string -> if inFileName names a readable, valid DocBook XML file -> output files named in lit-elts from that file := lit-content of those lit-elts sys.stderr +:= error messages from processing that file, if any else -> sys.stderr +:= error message ] """ #-- 1 -- fileMap = {} #-- 2 -- # [ if fileName names a readable, valid XML file -> # doc := an ElementTree representing that file # else -> # sys.stderr +:= error message(s) # return ] try: doc = etree.parse ( fileName ) except IOError, detail: print >>sys.stderr, ( "*** I/O error opening '%s': %s" % (fileName, detail) ) return except etree.XMLSyntaxError, detail: print >>sys.stderr, ( "*** Syntax error opening '%s': %s" % (fileName, detail) ) return #-- 3 -- # [ (doc is an etree Document) and # (fileMap is a dictionary whose keys are file names and # each corresponding value is a writeable file handle # for that file) -> # fileMap := fileMap with new file names added from # lit-dests in doc # file handles in fileMap := lit-content of those files # sys.stderr +:= error messages from processing doc, # if any ] processDoc ( fileMap, doc ) #-- 4 -- # [ fileMap is a dictionary whose values are file objects -> # those values := those values, closed ] for outFile in fileMap.values(): outFile.close() # - - - p r o c e s s D o c def processDoc ( fileMap, doc ): """Process one document tree [ (fileMap is a dictionary whose keys are file names and each corresponding value is a writeable file handle for that file) and (doc is an etree.ElementTree) -> fileMap := fileMap with new output files added from lit-elts in doc whose lit-dests can be opened anew files named in fileMap := lit-content of those files sys.stderr +:= error messages for lit-dests that cannot be opened for output, if any ] """ #-- 1 -- # [ eltList := a list of all the PROG_ELT elements in doc, # in document order ] root = doc.getroot() eltList = root.xpath ( "//" + PROG_ELT ) #-- 2 -- # [ fileMap := fileMap with new file names added from lit-dests # in eltList whose lit-dest files could be opened # files named in fileMap := lit-content of those files # sys.stderr +:= error messages from failures to open # those files, if any ] for elt in eltList: #-- 2 body -- # [ if (elt is a lit-elt) and # (lit-dest(elt) is a key in fileMap) -> # that value from fileMap +:= lit-content(elt) # else if (elt is a lit-elt) and # (lit-dest(elt) is not a key in fileMap) and # (a new file named lit-dest(elt) can be opened for # writing) -> # fileMap[lit-dest(elt)] := that new file # that new file +:= lit-content(elt) # else if (elt is a lit-elt) and # (lit-dest(elt) is not a key in fileMap) and # (a new file named lit-dest(elt) cannot be opened for # writing) -> # sys.stderr +:= error message # else -> I ] try: attrValue = elt.attrib[ROLE_ATTR] if attrValue.startswith ( ROLE_PREFIX ): outName = attrValue[len(ROLE_PREFIX):] processElt ( fileMap, outName, elt ) except KeyError: pass # - - - p r o c e s s E l t def processElt ( fileMap, outName, elt ): """Process one element that may be literate. [ (fileMap is a dictionary whose keys are file names and each corresponding value is a writeable file handle for that file) and (outName is a file name as a string) and (elt is an etree.Element) -> if fileMap has a key (outName) -> fileMap[outName] +:= text of elt else if outName can be opened new for writing -> fileMap[outName] := that file, so opened that file := text of elt else -> sys.stderr +:= error message(s) ] """ #-- 1 -- # [ if outName is a key of fileMap -> # I # else if outName can be opened new for writing -> # fileMap[outName] := that file, so opened # else -> # sys.stderr +:= error message(s) # return ] if not fileMap.has_key(outName): try: fileMap[outName] = open ( outName, "w" ) except IOError, detail: print >>sys.stderr, ( "*** Can't open '%s': %s" % (outName, detail) ) return #-- 2 -- # [ textList := a list of all text descendants of elt ] textList = elt.xpath ( "descendant-or-self::text()" ) #-- 3 -- # [ fileMap[outName] +:= elements of textList, concatenated ] fileMap[outName].write ( "".join ( textList ) ) # - - - - - e p i l o g u e - - - - - if __name__ == '__main__': main()