#!/usr/local/bin/python #-- # viewer.py: Constitution viewer # $Revision: 1.5 $ $Date: 2002/12/06 00:02:29 $ # Documentation is in http://www.nmt.edu/tcc/classes/cleanroom/spec.ps #-- # Overall intended function: # [ if (the CGI arguments are available and valid) # and (the input file specified by the arguments is readable # and conforms to constitution.dtd) -> # +:= a web page representing a view of that input # file as specified by the CGI arguments # else -> # +:= a web page describing some error(s) ] #-- # Contents: # imports # manifest constants # verification functions # classes: # class Args: Represents our CGI arguments # class Document: Represents the XML source document # class Constitution: Represents the Constitution # class Preamble: Represents a preamble-type element # class Article: Represents one article # class Section: Represents one section # class Para: Represents one paragraph # class Fragment: Abstract class for Para content # class Text(Fragment): Straight text content # class List(Fragment): Bullet list # class Xref(Fragment): Cross-reference to other item # class Verbatim(Fragment): Verbatim-formatted lines # class BookOfLaw: Represents the Book of Law # class LawArticle: Article parallel to a constitution article # class LawSection: Section parallel to a const. section # class Change: Represents one change in the Historical Record # class Interp: Represents one Supreme Court interpretation # class Resolution: Represent one Senate resolution # class View: Represents our view of the XML source document # main #================================================================ # Imports #---------------------------------------------------------------- import sys # System functions import string # String functions import os # Operating system stuff, e.g., envi. vars. import cgi # CGI argument processing import urllib # Necessary for certain quoting/unquoting functions import gen_xml # My personal XML generator package import foldifier # XML line-folding object #================================================================ # Manifest constants #---------------------------------------------------------------- FILE_NAME_ARG = "f" # File name passed via CGI interface LOCATION_ARG = "location" # Currently viewed location argument OUTLINER_ARG = "outliner" # Set of open outliner nodes QUERY_ARG = "query" # Search string, if any RESULT_ARG = "result" # Search hit number, if any MAX_LEVELS = 3 # Max length of a location-specifier LOC_DELIM = "." # Delimiter in a location-arg OUTLINER_DELIM = "," # Separates loc-specs in outliner-arg #---------------------------------------------------------------- # ..._GI: Generic identifiers (element types) # ..._ATTR: Attribute names #-- DOCUMENT_GI = "document" # CONSTITUTION_GI = "constitution" # PREAMBLE_GI = "preamble" # ARTICLE_GI = "article" #
TITLE_GI = "title" # Foreword SECTION_GI = "section" #
PARA_GI = "para" # LIST_GI = "list" # ... XREF_GI = "xref" # VERBATIM_GI = "verbatim" # ... BOOK_OF_LAW_GI = "bookoflaw" # LAW_ARTICLE_GI = "law-article" # LAW_SECTION_GI = "law-section" # HIST_RECORD_GI = "hist-record" # CHANGE_GI = "change" # TARGET_GI = "target" # ... COMMENTS_GI = "comments" # ... ORIGINAL_GI = "original" # ... INTERPRETATIONS_GI = "interpretations" # INTERP_GI = "interp" # SOURCE_GI = "source" # ... BODY_GI = "body" # ... SIGNATURE_GI = "signature" # ... SENATE_RESOLUTIONS_GI = "senate-resolutions" # RESOLUTION_GI = "resolution" # ... #================================================================ # Verification functions #---------------------------------------------------------------- # location-arg == # a string of 0 to 3 integers separated by LOC_DELIM, # interpreted as "[book[.article[.section]]]" #---------------------------------------------------------------- # location-specifier == # a list of 0 to 3 integers interpreted as [book, article, # section] #-- # This is used both in specifying folding/unfolding of the # outline and specifying the currently viewed location. # For example, in the outline, [] means all books are # folded; [3] means book 3 is unfolded but not its articles; # and [3,4] means book 3, article 4 is unfolded. #---------------------------------------------------------------- # outliner-arg == # a string of 0 or more location-args separated by # OUTLINER_DELIM #---------------------------------------------------------------- # outliner-specifier == # a list of 0 or more location-specifiers separated by # OUTLINER_DELIM #---------------------------------------------------------------- # - - - e r r o r P a g e - - - def errorPage ( *textList ): """Generates an HTML error page and halts. [ if textList is a list of strings -> +:= an HTML error page containing the concatenation of textList's element stop execution ] """" #-- 1 -- # [ text := elements of textList, concatenated ] text = "".join ( textList ) #-- 2 -- # [ +:= (generic error message) + (text) ] f = Foldifier ( sys.stdout, 80 ) root = BlockTag ( None, "html" ) head = BlockTag ( root, "head" ) title = BlockTag ( head, "title" ) pageTitle = "Constitution Viewer Error Page" title.add ( pageTitle ) body = BlockTag ( root, "body" ) p1 = BlockTag ( body, "p" ) # Standard paragraph p1.add ( "There is an error with the Constitution Viewer." ) p2 = BlockTag ( body, "p" ) p2.add ( "".join ( textList ) ) root.str ( f ) #-- 3 -- # [ stop execution ] sys.exit(1) # - - - - - c l a s s A r g s - - - - - class Args: """Represents all the CGI arguments passed to this script. Exports: Args(): [ if the CGI arguments are available and syntactically valid -> returns an Args object that represents those arguments else -> +:= an error page stop execution ] .in: [ name of the input file; READ-ONLY ] .location: [ a location-specifier describing the currently viewed location ] .outliner: [ an outliner-specifier describing what elements of the outline are currently unfolded ] .query: [ if there is no query argument -> None else -> the query string ] .result: [ if there is no query argument -> None else -> the index of the current search result, as an integer, counting from 0, or 0 if this is the first search ] """ # - - - A r g s . _ _ i n i t _ _ - - - # Verified 2002-11-7: Lyons, Lombardo, Becker def __init__ ( self ): "Constructor for Args" #-- 1 -- # [ if CGI arguments are available -> # form := a cgi.FieldStorage object representing the # CGI arguments to this script # else -> # +:= an error page # stop execution ] if not os.environ.has_key("GATEWAY_INTERFACE"): errorPage("This script is intended for use with the ", "CGI interface." ) else: form = cgi.FieldStorage() #-- 2 -- # [ if form has a FILE_NAME_ARG element -> # self.in := the value of that argument # else -> # +:= an error page # stop execution ] try: self.in = form[FILE_NAME_ARG] except KeyError: errorPage ( "Programming error: missing `%s' argument." % FILE_NAME_ARG ) #-- 3 -- # [ if form has a syntactically valid LOCATION_ARG element -> # self.location := that element's value as a # location-specifier # else if LOCATION_ARG is present but syntactically invalid -> # +:= an error page # stop execution # else -> # self.location := an empty list ] self.__processLocation ( form ) #-- 4 -- # [ if form has a syntactically valid OUTLINER_ARG element -> # self.outliner := that element's value as a list # of location-specifiers # else if OUTLINER_ARG is present but syntactically invalid -> # +:= an error page # stop execution # else -> # self.outliner := an empty list ] self.__processOutliner ( form ) #-- 5 -- # [ if form has syntactically valid QUERY_ARG and RESULT_ARG # elements -> # self.query := as invariant, from form # self.result := as invariant, from form # else -> # +:= an error page # stop execution ] self.__processQueryArgs ( form ) # - - - A r g s . _ _ p r o c e s s L o c a t i o n - - - # Verified 2002-11-07: Lyons, Lombardo, Becker def __processLocation ( self, form ): """Process the LOCATION_ARG argument, if present [ if form acts like a dictionary -> if form has a syntactically valid LOCATION_ARG element -> self.location := that element's value as a location-specifier else if LOCATION_ARG is present but syntactically invalid -> +:= an error page stop execution else -> self.location := an empty list ] """ #-- 1 -- # [ if form has a LOCATION_ARG element -> # rawLoc := the value of that element # else -> # self.location := an empty list # return ] try: rawLoc = form[LOCATION_ARG] except KeyError: self.location = [] return #-- 2 -- # [ if rawLoc is a syntactically valid LOCATION_ARG element -> # self.location := that element as a location-specifier # else -> # +:= an error page # stop execution ] self.location = self.__parseLocSpecifier ( rawLoc ) # - - - A r g s . _ _ p a r s e L o c S p e c i f i e r - - - # Verified 2002-11-07: Becker, Lombardo, Thomas def __parseLocSpecifier ( self, s ): """Check s is a valid location-arg and convert to location-specifier [ if s is a string -> if s is a valid location-arg -> return s as a location-specifier else -> +:= an error page stop execution ] """ #-- 1 -- # [ splitLoc := s, split apart wherever there are LOC_DELIM # localResult := an empty list ] splitLoc = s.split(LOC_DELIM) localResult = [] #-- 2 -- # [ if splitLoc has more than MAX_LEVELS elements -> # +:= an error page # stop execution # else -> I ] if len(splitLoc) > MAX_LEVELS: errorPage ( "A location specifier can have no more than " "%d elements." % MAX_LEVELS ) #-- 3 -- # [ if any element of splitLoc is not an integer in string form -> # +:= an error page # stop execution # else -> # localResult +:= those elements as integers ] for item in splitLoc: #-- 3 body -- # [ if item is an integer in string form -> # localResult +:= item in integer form # else -> # +:= an error page # stop execution ] try: localResult.append ( int ( item ) ) except ValueError: errorPage ( "Sorry, `%s' is not a valid integer." % item ) #-- 4 -- return localResult # - - - A r g s . _ _ p r o c e s s O u t l i n e r - - - def __processOutliner ( self, form ): """Process the OUTLINER_ARG, if any. [ if form acts like a dictionary -> if form has a syntactically valid OUTLINER_ARG element -> self.outliner := that element's value as a list of location specifiers else if OUTLINER_ARG is present but syntactically invalid -> +:= an error page stop execution else -> self.outliner := an empty list ] """ #-- 1 -- # [ if form has no OUTLINER_ARG element -> # self.outliner := an empty list # return # else -> # rawOutliner := that element ] try: rawOutliner = form[OUTLINER_ARG] except KeyError: self.outliner = [] return #-- 2 -- # [ if rawOutliner is a syntactically valid outliner-arg -> # self.outliner := rawOutliner as an outliner-specifier # else -> # +:= an error page # stop execution ] self.outliner = self.__parseOutliner ( rawOutliner ) # - - - A r g s . _ _ p a r s e O u t l i n e r - - - def __parseOutliner ( self, s ): """Check the list of open outline nodes and convert. [ if s is a string -> if s is a syntactically valid outliner-arg element -> return s as an outliner-specifier else -> +:= an error page stop execution ] """ #-- 1 -- # [ splitOutliner := s, broken into pieces on OUTLINER_DELIM # localResult := a new, empty list ] splitOutliner = s.split ( OUTLINER_DELIM ) localResult = [] #-- 2 -- # [ if each of the elements of splitOutliner is a valid # location-arg -> # localResult +:= those elements converted to location-specifiers # else -> # +:= an error page # stop execution ] for rawLoc in splitOutliner: #-- 2 body -- # [ if rawLoc is a valid location-arg -> # localResult +:= rawLoc as a location-specifier # else -> # +:= an error page # stop execution ] localResult.append ( self.__parseLocSpecifier ( rawLoc ) ) #-- 3 -- return localResult # - - - A r g s . _ _ p r o c e s s Q u e r y A r g s - - - def __processQueryArgs ( self, form ): """Process the QUERY_ARG and RESULT_ARG elements [ if form acts like a dictionary with string values -> if form has syntactically valid QUERY_ARG and RESULT_ARG elements -> self.query := as invariant, from form self.result := as invariant, from form else -> +:= an error page stop execution ] """ #-- 1 -- # [ if form has no QUERY_ARG and no RESULT_ARG -> # self.query := None # self.result := None # else form has only one but not the other -> # +:= an error page # stop execution # else -> # rawQuery := form[QUERY_ARG] # rawResult := form[RESULT_ARG] ] rawQuery = form.get(QUERY_ARG, None) rawResult = form.get(RESULT_ARG, None) if ( ( rawQuery is None ) and (rawResult is None ) ): self.query = self.result = None return elif ( ( rawQuery is None ) or ( rawResult is None ) ): errorPage ( "Both query and result args must be specified." ) #-- 2 -- # [ if neither rawQuery nor rawResult is None -> # if rawResult is convertible to an integer -> # self.result := rawResult converted to integer # self.query := rawQuery # else -> # +:= an error page # stop execution ] try: self.result = int ( rawResult ) self.query = rawQuery except ValueError: errorPage ( "Result argument must be an integer." ) # - - - - - c l a s s D o c u m e n t - - - - - class Document: """Represents a file conforming to constitution.dtd. Exports: Document ( inFile ): [ if inFile is a string -> if the file named by inFile is readable and conforms to constitution.dtd -> return a new Document object representing that file else -> +:= an error page stop execution ] .inFile: [ as passed to constructor, read-only ] .constitution: [ self's constitution as a Constitution object ] .bookOfLaw: [ self's book of law as a BookOfLaw object ] .histRecord: [ self's historical record as a list of Change objects ] .interpretations: [ self's Supreme Court interpretations as a list of Interp objects ] .senateResolutions: [ self's Senate resolutions as a list of Resolution objects ] """ # - - - D o c u m e n t . _ _ i n i t _ _ - - - def __init__ ( self, inFile ): "Constructor for constitution Document" #-- 1 -- self.inFile = inFile #-- 2 -- # [ if inFile names a readable file that conforms to # constitution.dtd -> # doc := a DOM Document object that represents inFile # else -> # raise IOError ] \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\ #-- 3 -- # [ self.constitution := doc's CONSTITUTION_GI element as a # Constitution object ] \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\ #-- 4 -- # [ self.bookOfLaw := doc's BOOK_OF_LAW_GI element as a # BookOfLaw object ] \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\ # - - - - - c l a s s C o n s t i t u t i o n - - - - - class Constitution: """Represents the constitution. Exports: Constitution( """ \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\ # - - - - - c l a s s P r e a m b l e - - - - - class Preamble: """Represents a preamble-type element. """ \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\ # - - - - - c l a s s A r t i c l e - - - - - class Article: """Represents one article. """ \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\ # - - - - - c l a s s S e c t i o n - - - - - class Section: """Represents one section. """ \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\ # - - - - - c l a s s P a r a - - - - - class Para: """Represents the element, a structured text paragraph. """ \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\ # - - - - - c l a s s F r a g m e n t - - - - - class Fragment: """Abstract class for the children of Para. """ \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\ # - - - - - c l a s s T e x t - - - - - class Text(Fragment): """Represents #PCDATA in a paragraph. """ \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\ # - - - - - c l a s s L i s t - - - - - class List(Fragment): """Represents a list of items within a paragraph. """ \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\ # - - - - - c l a s s X r e f - - - - - class Xref(Fragment): """Represents a cross-reference to a different article/section/para. """ \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\ # - - - - - c l a s s V e r b a t i m - - - - - class Verbatim(Fragment): """Represents a group of lines to be presented verbatim. """ \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\ # - - - - - c l a s s B o o k O f L a w - - - - - class BookOfLaw: """Represents the Book of Law. """ \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\ # - - - - - c l a s s L a w A r t i c l e - - - - - class LawArticle: """Represents a Book of Law article parallel to a constitution article. """ \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\ # - - - - - c l a s s L a w S e c t i o n - - - - - class LawSection: """Represents a Book of Law section parallel to a constitution section. """ \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\ # - - - - - c l a s s C h a n g e - - - - - class Change: """Represents one change in the Historical Record """ \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\ # - - - - - c l a s s I n t e r p - - - - - class Interp: """Represents one Supreme Court interpretation. """ \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\ # - - - - - c l a s s R e s o l u t i o n - - - - - class Resolution: """Representns one Senate resolution. """ \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\ \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\ # - - - - - c l a s s V i e w - - - - - class View: """Represents a Web page with a particular view of a document Exports: View ( args, document ): [ if (args is an Args object) and (document is a Document object) -> return a new View object representing a view of document according to args ] .__str__(self): [ return a web page representing self ] """ \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\ # - - - - - m a i n - - - - - # Verified 2002-10-17: D. Lyons, P. Paquette, J. Lombardo #-- 1 -- # [ +:= standard CGI headers indicating that we'll write # an HTML page ] print "Content-type: text/html" print #-- 2 -- # [ if the CGI arguments are available and valid -> # args := an Args object that represents those arguments # else -> # +:= an error page # stop execution ] args = Args() #-- 3 -- # [ if the input file specified by args.in is readable # and conforms to constitution.dtd -> # document := a Document object representing that file # else -> # +:= an error page # stop execution ] document = Document(args.in) #-- 4 -- # [ view := a View object representing a view of document # as specified by args ] view = View(args, document) #-- 5 -- # [ +:= a web page representing view ] print view