# $Id: N3Report.py,v 1.28 2004/08/16 12:56:00 graham Exp $ # # RDF report generator # # Uses an RDF-based description stored in an N3Model to generate a report # from the content of an N3Model # #--------+---------+---------+---------+---------+---------+---------+---------+ # # Copyright (c) 2002, G. KLYNE # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions # are met: # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # 3. The name of the author may not be used to endorse or promote products # derived from this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR # IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES # OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. # IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT # NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF # THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # #--------+---------+---------+---------+---------+---------+---------+---------+ # $Source: /file/cvsdev/PythonN3/N3Report.py,v $ # $Author: graham $ # $Revision: 1.28 $ $Date: 2004/08/16 12:56:00 $ #--------+---------+---------+---------+---------+---------+---------+---------+ # 1 2 3 4 5 6 7 8 #import os #import re import sys import string import StringIO from StringBuffer import StringBuffer from N3Exception import N3Exception from N3Node import N3Node from N3Statement import N3Statement from N3Model import N3Model from N3Model import N3Vocab #--------+---------+---------+---------+---------+---------+---------+---------+ # Define N3Model package exception conditions # # Base class for exceptions # class N3ReportError( N3Exception ): """ Base class for N3Report generation errors. Attributes: message -- explanation of the error """ def __init__(self, message): N3Exception.__init__( self, "N3ReportError: "+message ) pass #--------+---------+---------+---------+---------+---------+---------+---------+ # N3Variables - defines a current set of variable bindings # class N3Variables: # Essentially functions as a dictionary of variable bindings. # # A N3Variables object is created from a previous existing N3Variables # object, or None, and a set of additional variable bindings. # # Bindings are from names to N3Node objects. # def __init__(self,previous,newBindings): self.m_prev = previous self.m_bind = newBindings # Return value associated with a supplied name, or 'None' # def value(self,name): v = self while v: if name in v.m_bind.keys(): return v.m_bind[name] v = v.m_prev return None # Test to see if a supplied set of bindings are compatible with # the current working value # # Returns a dictionary of values in 'bindings' that are NOT defined in the # current working value if matched, otherwise 'None' if the new bindings # are not compatible with those present. Also returns null if a name is # defined inconsistently in the supplied bindings. # # 'bindings' is a list of [name, node] pairs. # def findDifferences(self, bindings): newBindings = {} for name,bval in bindings: v = self.value( name ) if not v: v = newBindings.get(name,None) if v: # name already defined - check for same value if v != bval: return None else: # name not defined here - add new binding newBindings[name] = bval return newBindings # Enumerate current variable bindings to supplied stream def enumBindings(self,str): v = self c = 0 str.write('{') while v: for name in v.m_bind.keys(): if c: str.write( ',' ) str.write( name + ':' ) if v.m_bind[name]: str.write( v.m_bind[name] ) else: str.write( "(None)" ) c = 1 v = v.m_prev str.write('}\n') return None def debugShow( self, num ): if num == 0: self.debug( "Variable bindings:" ) self.debug( "Map["+str(num)+"] "+`self.m_bind` ) if self.m_prev: self.m_prev.debugShow( num+1 ) return def debug( self, msg ): sys.stdout.write( "N3Variables: "+msg+"\n" ) return # End of N3Variables #--------+---------+---------+---------+---------+---------+---------+---------+ # N3QueryTriple - query pattern for matching a single triple # class N3QueryTriple: # Constructor: Subj, Pred and Obj are graph nodes corresponding to the # subject, predicate and object of the triple. Each may be one of the # following: # [ rep:var "name" ] # [ rep:uri uriref ] # [ rep:lit "string" ] # def __init__( self, Model, Subj, Pred, Obj ): self.Model = Model self.Subj = Subj self.Pred = Pred self.Obj = Obj # Force no value return if next() called before init(): self.List = None return # Local helper method: construct a node object that takes account of # supplied variable bindings. # # If the node is a query variable that is already bound, then the node # to which it is bound is returned, otherwise the supplied value is # returned. The goal is to avoid doing query searches for names that # have already been bound. # def makeQueryNode( self, Node, Variables ): if Node.isBlank(): Name = Node.getLocalName() if Name: Binding = Variables.value( Name ) if Binding: return Binding return Node # Initialize query pattern # # The supplied variable bindings correspond to query patterns matched # so far. # def init( self, Variables ): s = self.makeQueryNode( self.Subj, Variables ) p = self.makeQueryNode( self.Pred, Variables ) o = self.makeQueryNode( self.Obj, Variables ) self.debug( "init N3QueryTriple: "+ self.name(self.Subj)+","+ self.name(self.Pred)+","+ self.name(self.Obj) ) self.debug( " Query nodes : "+ self.name(s)+","+ self.name(p)+","+ self.name(o) ) self.Vars = Variables self.sVar = s.getVariableName() self.pVar = p.getVariableName() self.oVar = o.getVariableName() # Pass variable query nodes as None, # so they can match any graph node. # This assumes that variable nodes appear only in a query pattern, # and non-variable blank nodes only in the graph. Variables can # be bound to specific blank nodes in the graph. if s.isVariable(): s = None if p.isVariable(): p = None if o.isVariable(): o = None self.List = self.Model.findStatements( s, p, o ) if self.List: self.debug( " Number of matches: "+str(len(self.List)) ) else: self.debug( " Empty list returned" ) ### Variables.debugShow( 0 ) return # Return variable bindings for next query value # # If there is another triple in the graph matching the supplied statement # then return it, otherwise return None. # def next( self ): while self.List: NextBind = [] NextStmt = self.List[0] self.debug( "NextStmt "+str(NextStmt) ) del self.List[0] if self.sVar: NextBind.append( [self.sVar,NextStmt.getSubject()] ) if self.pVar: NextBind.append( [self.pVar,NextStmt.getProperty()] ) if self.oVar: NextBind.append( [self.oVar,NextStmt.getObject()] ) NewVars = self.Vars.findDifferences( NextBind ) if NewVars != None: if NewVars: self.debug( "New bindings: "+`NewVars` ) return N3Variables( self.Vars, NewVars ) return self.Vars # Incompatible variable bindings: try for next triple # No more: free up scanning resources self.close() return None # Release all resources used for this query # def close( self ): self.Vars = None self.sVar = None self.pVar = None self.oVar = None self.List = None return def debug( self, msg ): ### sys.stdout.write( "N3QueryTriple: "+msg+"\n" ) return def name( self, node ): return self.Model.getNodeName( node ) # End of N3QueryTriple #--------+---------+---------+---------+---------+---------+---------+---------+ # N3QueryContainerMembers - query pattern for matching a member of a container # # This is mostly based on N3QueryTriple, but supplies a modified next() method. # # N3QueryTriple # class N3QueryContainerMembers( N3QueryTriple ): # Constructor: Subj, Obj are graph nodes corresponding to the # container, and container member to be matched. # # Note that N3QueryTriple.init initializes self.List to be a list # of statements that match the supplied subj,pred,obj values. # def __init__( self, Model, Subj, Obj ): N3QueryTriple.__init__( self, Model, Subj, N3Node(name="_"), Obj ) return # Return variable bindings for next query value # # If there is another matching container member in the graph # then return it, otherwise return None. # def next( self ): while self.List: NextStmt = self.List[0] # self.debug( "NextStmt "+str(NextStmt) ) del self.List[0] # Check property is container membership p = NextStmt.getProperty() if not p.isURI(): continue if p != N3Vocab.rdfs_contains: # Property name must be of form 'rdf:_n': if p.getNamespace() != self.Model.getPrefixTable()["rdf"]: continue ln = p.getLocalName() if ln[0] != "_" or not ln[1:].isdigit(): continue # Now check container and member node bindings NextBind = [] if self.sVar: NextBind.append( [self.sVar,NextStmt.getSubject()] ) if self.oVar: NextBind.append( [self.oVar,NextStmt.getObject()] ) NewVars = self.Vars.findDifferences( NextBind ) if NewVars != None: if NewVars: self.debug( "New bindings: "+`NewVars` ) return N3Variables( self.Vars, NewVars ) return self.Vars # Incompatible variable bindings: try for next triple # No more: free up scanning resources self.close() return None # End of N3QueryContainerMembers #--------+---------+---------+---------+---------+---------+---------+---------+ # N3QueryListElements - query pattern for matching elements of selected list(s) # # This is mostly based on N3QueryTriple, but supplies a modified init() and # next() methods. # class N3QueryListElements( N3QueryTriple ): # Constructor: Subj, Obj are graph nodes corresponding to the # container, and container member to be matched. # # Note that N3QueryTriple.init initializes self.List to be a list # of statements that match the supplied subj,pred,obj values. # # Note that, while not prohibited, the supplied subject should not be # unbound variable, otherwise every intermediate list constructor of # every list may be matched and returned, which is most unlikely # to be what was intended. # def __init__( self, Model, Subj, Obj ): N3QueryTriple.__init__( self, Model, Subj, N3Vocab.rdf_first, Obj ) self.debug( "Subj "+str(Subj) ) self.debug( "Obj "+str(Obj) ) # Force no value return if next() called before init(): self.ListHead = None self.ListCons = None return # Initialize query pattern # # The supplied variable bindings correspond to query patterns matched # so far. # def init( self, Variables ): self.debug( "init" ) ### Variables.debugShow( 0 ) N3QueryTriple.init( self, Variables ) self.debug( "List "+str(self.List) ) return # Return variable bindings for next query value # # If there is another matching container member in the graph # then return it, otherwise return None. # # NOTE: # self.ListHead is matched head of the current list # self.ListCons is next list constructor in current list # def next( self ): while self.ListHead or self.List: if not self.ListCons or self.ListCons == N3Vocab.rdf_nil: # Select next listhead if not self.List: break # No more self.ListHead = self.List[0].getSubject() del self.List[0] self.ListCons = self.ListHead # Find next list element NextElem = self.Model.findObjectValue( self.ListCons, N3Vocab.rdf_first ) self.debug( "NextElem "+str(NextElem) ) self.ListCons = self.Model.findObjectValue( self.ListCons, N3Vocab.rdf_rest ) if not NextElem: continue # Now check container and member node bindings NextBind = [] if self.sVar: NextBind.append( [self.sVar,self.ListHead] ) if self.oVar: NextBind.append( [self.oVar,NextElem] ) NewVars = self.Vars.findDifferences( NextBind ) if NewVars != None: if NewVars: self.debug( "New bindings: "+`NewVars` ) return N3Variables( self.Vars, NewVars ) return self.Vars # Incompatible variable bindings: try for next triple # No more: free up scanning resources self.close() return None # Release all resources used for this query # def close( self ): N3QueryTriple.close( self ) self.ListHead = None self.ListCons = None return def debug( self, msg ): ### sys.stdout.write( "N3QueryListElements: "+msg+"\n" ) return # End of N3QueryListElements #--------+---------+---------+---------+---------+---------+---------+---------+ # N3QueryNull - null query pattern - always matches once # class N3QueryNull: # Constructor: no arguments # def __init__( self ): self.Variables = None # Force no return on next() before init() return # Initialize query pattern # # The supplied variable bindings correspond to query patterns matched # so far. # def init( self, Variables ): self.Variables = Variables return # Return variable bindings for next query value # # If there is another triple in the graph matching the supplied statement # then return it, otherwise return None. # def next( self ): Result = self.Variables self.Variables = None return Result # Release all resources used for this query # def close( self ): return # End of N3QueryNull #--------+---------+---------+---------+---------+---------+---------+---------+ # N3QuerySequence - query pattern for matching a sequence of triples # class N3QuerySequence: # Constructor: provide a sequence of N3QueryTriple and other primitive # query values (see above), and (optionally) an alternate sequence. # # Parameters: # Model model to be queried # Sequence a sequence of query patterns to be concurrently # matched against the model. # Alt an alternative sequence of triples to be matched # if the original sequence is not matched, or None # if no alternative match is allowed. # # Note: an empty sequence always matches. # def __init__( self, Model, Sequence, Alt ): if Sequence == None: raise N3ReportError( "N3QuerySequence with null sequence" ) self.Model = Model self.Sequence = Sequence self.Alternate = Alt self.UseSeq = [] # Force next() before init() to return None return # Initialize query pattern # # The supplied variable bindings correspond to query patterns matched # so far. # def init( self, Variables ): # Initialize the first node in the sequence. # Backtracking will force initialization of the rest. # (Note: the N3QueryTriple constructor ensures that an immediate # call of next() will return None.) # # One of two sequences is used: self.Sequence if a match can be # found, otherwise self.Alternate if defined and no match for # self.Sequence is found. # self.UseAlt = self.makePattern( self.Alternate, Variables ) self.UseSeq = self.makePattern( self.Sequence, Variables ) if not self.UseSeq: self.UseSeq = self.UseAlt self.UseAlt = None return # Local helper function: select and initialize and return # a pattern sequence # # An empty sequence is replaced by a sequence of N3QueryNull # def makePattern( self, PatSeq, Variables ): if PatSeq != None: if len(PatSeq) == 0: PatSeq = [ N3QueryNull() ] PatSeq[0].init( Variables ) return PatSeq # Return variable bindings for next query value # # Backtrack from end of sequence until a node returns a new value, # then work forward to end of sequence, backtracking as necessary # if subsequent nodes have no new values. # # If no match from main sequence, try alternate sequence # # Termination conditions: # - on reaching end of sequence with new variable binding, return that. # - on backtracking to start of sequence (or alternate), return None. # def next( self ): ### self.debug( "next("+str(id(self))+")" ) End = len(self.UseSeq) Next = End-1 while Next >= 0: Vars = self.UseSeq[Next].next() if Vars: Next += 1 if Next >= End: # Match found, don't use alt self.UseAlt = None return Vars self.Sequence[Next].init( Vars ) else: Next -= 1 if ( Next < 0 ) and ( self.UseAlt != None ): # Switch to alternate sequence self.UseSeq = self.UseAlt self.UseAlt = None End = len(self.UseSeq) Next = End-1 self.close() return None # Release all resources used for this query # def close( self ): if self.Sequence: for s in self.Sequence: s.close() if self.Alternate: for s in self.Alternate: s.close() return # Debug write def debug( self, msg ): sys.stdout.write( "N3QuerySequence: "+msg+"\n" ) return # End of N3QuerySequence #--------+---------+---------+---------+---------+---------+---------+---------+ # N3Formatter - format variables into buffer # # Classes are: # N3Formatter( FormatSeq ) # N3FormatString( StrVal ) # N3FormatNewline() # N3FormatTab( Pos, Space=None, Newline=None ) # N3FormatMargins( Left=-1, Wrap=-1, Indent=0 ) # N3FormatIf( Defined, FormatT, FormatF, Any=0 ) # N3FormatDefer( Option, Format ) # class N3Formatter: """ N3Formatter - object to format a report into a supplied buffer The main object method is 'format()'. """ def __init__( self ): return def format( self, Variables, Buffer ): Buffer.append( " [[[Abstract N3Formatter]]] " ) return # Return string value of formatter object def getString( self, Variables ): Buffer = StringBuffer() self.format( Variables, Buffer ) return Buffer.getString() # Debug write def debug( self, msg ): sys.stdout.write( "N3Formatter: "+msg+"\n" ) return # End of class N3Formatter class N3FormatSequence( N3Formatter ): """ N3FormatSequence - object to format a sequence of values into a buffer N3FormatSequence( None ) can also be used as null format template """ def __init__( self, FormatterSeq ): self.FormatterSeq = FormatterSeq return def format( self, Variables, Buffer ): if self.FormatterSeq: for f in self.FormatterSeq: f.format( Variables, Buffer ) return # End of class N3FormatSequence class N3FormatString( N3Formatter ): """ N3FormatString - formats a specified string to a supplied buffer. """ def __init__( self, StrVal ): if StrVal == None: self.len = len(StrVal) self.StrVal = StrVal return def format( self, Variables, Buffer ): Buffer.append( self.StrVal ) return # End of class N3FormatString class N3FormatVariable( N3Formatter ): """ N3FormatVariable - formats a named variable to a supplied buffer. If supplied, 'local' is true to display the Qname local part only of a URI. """ def __init__( self, VarName, local=0 ): self.VarName = VarName self.Local = local return def format( self, Variables, Buffer ): varNode = Variables.value( self.VarName ) if varNode: if varNode.isURI(): if self.Local: Buffer.append( varNode.getLocalName() ) else: Buffer.append( varNode.getURI() ) elif varNode.isLiteral(): Buffer.append( varNode.getString() ) elif varNode.isBlank(): Buffer.append( varNode.getIdent() ) else: self.debug( "Unknown node type, name: "+ self.VarName+", node: "+str(varNode) ) Buffer.append( " [[["+self.VarName+": "+str(varNode)+"]]] " ) else: self.debug( "Undefined variable: "+self.VarName ) Buffer.append( " [[[" + self.VarName + "?]]] " ) return # End of class N3FormatVariable class N3FormatNewline( N3Formatter ): """ N3FormatNewline - formats a newline to the supplied buffer """ def __init__( self ): return def format( self, Variables, Buffer ): Buffer.newline() return # End of class N3FormatNewline class N3FormatTab( N3Formatter ): """ N3FormatTab - formats a tab or spaces in the supplied buffer """ def __init__( self, Pos, Space=None, Newline=None ): self.Pos = Pos self.Sp = Space self.Nl = Newline return def format( self, Variables, Buffer ): if self.Nl: Buffer.tabnl( self.Pos ) elif self.Sp: Buffer.tabsp( self.Pos ) else: Buffer.tab( self.Pos ) return # End of class N3FormatTab class N3FormatTrim( N3Formatter ): """ N3FormatTrim - trims trailing spaces from the last item in the format buffer """ def __init__( self ): return def format( self, Variables, Buffer ): Buffer.trim() return # End of class N3FormatTrim class N3FormatMargins( N3Formatter ): """ N3FormatMargins - defines formatting margins """ def __init__( self, Left=-1, Wrap=-1, Indent=0 ): self.Left = Left self.Indent = Indent self.Wrap = Wrap return def format( self, Variables, Buffer ): if self.Left >= 0: Buffer.setLeft( self.Left ) if self.Wrap >= 0: Buffer.setWrap( self.Wrap ) if self.Indent: Buffer.indent( self.Indent ) return # End of class N3FormatMargins class N3FormatIf( N3Formatter ): """ N3FormatIf - defines formatting margins """ def __init__( self, Defined, FormatT, FormatF, Any=0 ): self.Any = Any self.Defined = Defined self.FormatT = FormatT self.FormatF = FormatF return def format( self, Variables, Buffer ): #### #### Buffer.append( " [[[Truepath: " ) #### self.FormatT.format( Variables, Buffer ) #### Buffer.append( " ]]] " ) #### Buffer.newline() #### Buffer.append( " [[[Elsepath: " ) #### self.FormatF.format( Variables, Buffer ) #### Buffer.append( " ]]] " ) #### Buffer.newline() #### if self.Any: #### Buffer.append( " [[[ifany "+repr(self.Defined)+"]]] " ) #### Select = self.FormatF for Nam in self.Defined: if Variables.value( Nam ): #### Buffer.append( " [[["+Nam+"defined]]] " ) #### Select = self.FormatT break else: #### Buffer.append( " [[[ifall "+repr(self.Defined)+"]]] " ) #### Select = self.FormatT for Nam in self.Defined: if not Variables.value( Nam ): #### Buffer.append( " [[["+Nam+"not defined]]] " ) #### Select = self.FormatF break if Select: Select.format( Variables, Buffer ) return # End of class N3FormatIf class N3FormatDefer( N3Formatter ): """ N3FormatDefer - object to format a deferred output sequence This is used for dealing with separators and terminators in lists containing optional elements. Constructor parameters: Option 0 to save defered value, or 1 to flush defered value Format Formatter value to save or output. """ def __init__( self, Option, Format ): self.Option = Option self.Format = Format return def format( self, Variables, Buffer ): if self.Option == 0: Buffer.defer( self.Format.getString( Variables ) ) elif self.Option == 1: Buffer.flush( self.Format.getString( Variables ) ) else: Buffer.append( " [[[Defer option: "+ str(Option)+": "+ self.StrVal+"]]] " ) return # End of class N3FormatDefer #--------+---------+---------+---------+---------+---------+---------+---------+ # N3Report - defines methods to generate a report using N3Model data # class N3Report: """ N3Report - object to generate reports from a model. The reports themselves are defined by RDF structures. """ #--------------------------------------------------------------------------- # Construction and setup methods #--------------------------------------------------------------------------- def __init__( self, Model, ReportNode ): """ Construct a report object from the supplied model and report definition node. """ self.Model = Model self.Node = ReportNode self.Next = None self.Channels = {} self.FormatterCache = {} self.PatternCache = {} rep_ns = "http://id.ninebynine.org/wip/2002/ReportGen/" self.rep_Report = N3Node( ns=rep_ns, name="Report" ) self.rep_cmd = N3Node( ns=rep_ns, name="cmd" ) self.rep_var = N3Node( ns=rep_ns, name="var" ) self.rep_uri = N3Node( ns=rep_ns, name="uri" ) self.rep_local = N3Node( ns=rep_ns, name="local" ) self.rep_lit = N3Node( ns=rep_ns, name="lit" ) self.rep_open = N3Node( ns=rep_ns, name="open" ) self.rep_chan = N3Node( ns=rep_ns, name="chan" ) self.rep_file = N3Node( ns=rep_ns, name="file" ) self.rep_write = N3Node( ns=rep_ns, name="write" ) self.rep_debug = N3Node( ns=rep_ns, name="debug" ) self.rep_data = N3Node( ns=rep_ns, name="data" ) self.rep_close = N3Node( ns=rep_ns, name="close" ) self.rep_for = N3Node( ns=rep_ns, name="for" ) self.rep_if = N3Node( ns=rep_ns, name="if" ) self.rep_ifany = N3Node( ns=rep_ns, name="ifany" ) self.rep_pattern = N3Node( ns=rep_ns, name="pattern" ) self.rep_defined = N3Node( ns=rep_ns, name="defined" ) self.rep_do = N3Node( ns=rep_ns, name="do" ) self.rep_first = N3Node( ns=rep_ns, name="first" ) self.rep_last = N3Node( ns=rep_ns, name="last" ) self.rep_else = N3Node( ns=rep_ns, name="else" ) self.rep_sep = N3Node( ns=rep_ns, name="sep" ) self.rep_and = N3Node( ns=rep_ns, name="and" ) self.rep_alt = N3Node( ns=rep_ns, name="alt" ) self.rep_opt = N3Node( ns=rep_ns, name="opt" ) self.rep_member = N3Node( ns=rep_ns, name="member" ) self.rep_element = N3Node( ns=rep_ns, name="element" ) self.rep_nl = N3Node( ns=rep_ns, name="nl" ) self.rep_tab = N3Node( ns=rep_ns, name="tab" ) self.rep_tabsp = N3Node( ns=rep_ns, name="tabsp" ) self.rep_tabnl = N3Node( ns=rep_ns, name="tabnl" ) self.rep_left = N3Node( ns=rep_ns, name="left" ) self.rep_indent = N3Node( ns=rep_ns, name="indent" ) self.rep_wrap = N3Node( ns=rep_ns, name="wrap" ) self.rep_defer = N3Node( ns=rep_ns, name="defer" ) self.rep_flush = N3Node( ns=rep_ns, name="flush" ) self.rep_trimws = N3Node( ns=rep_ns, name="trimws" ) # Type-check node RepType = Model.findObjectValue( self.Node, N3Vocab.rdf_type ) if RepType != self.rep_Report: raise N3ReportError( "Report must have type "+str(self.rep_Report) ) return def getModel( self ): return self.Model #--------------------------------------------------------------------------- # Report generation methods #--------------------------------------------------------------------------- def generate( self, InitDict ): """ Generate a report using the supplied dictionary as the initial set of variable bindings. """ self.Channels = {} self.list_first = self.Model.makeSpecialNode( "listfirst" ) self.list_rest = self.Model.makeSpecialNode( "listrest" ) self.list_null = self.Model.makeSpecialNode( "listnull" ) Variables = N3Variables( None, InitDict ) self.doBody( self.Node, Variables ) for c in self.Channels.keys(): if self.Channels[c]: self.Channels[c].close() return def doBody( self, Next, Variables ): """ Process the sequence of report generation steps linked from the given node, using the given variable bindings. If the supplied Next node is null, no action is taken. """ while Next and ( Next != self.list_null ): Item = self.Model.findObjectValue( Next, self.list_first ) Next = self.Model.findObjectValue( Next, self.list_rest ) cmd = self.Model.findObjectValue( Item, self.rep_cmd ) if cmd == self.rep_open: chan = self.Model.findObjectValue( Item, self.rep_chan ) file = self.Model.findObjectValue( Item, self.rep_file ) self.doOpen( self.findStringValue( chan, Variables ), self.findStringValue( file, Variables ) ) elif cmd == self.rep_close: chan = self.Model.findObjectValue( Item, self.rep_chan ) self.doClose( self.findStringValue( chan, Variables ) ) elif cmd == self.rep_write: chan = self.Model.findObjectValue( Item, self.rep_chan ) data = self.Model.findObjectValue( Item, self.rep_data ) self.doWrite( self.findStringValue( chan, Variables ), data, Variables ) elif cmd == self.rep_debug: data = self.Model.findObjectValue( Item, self.rep_data ) self.doDebug( data, Variables ) elif cmd == self.rep_if: defined = self.Model.findObjectValue( Item, self.rep_defined ) pattern = self.Model.findObjectValue( Item, self.rep_pattern ) bodydo = self.Model.findObjectValue( Item, self.rep_do ) bodyelse = self.Model.findObjectValue( Item, self.rep_else ) self.doIf( defined, pattern, bodydo, bodyelse, Variables ) elif cmd == self.rep_ifany: defined = self.Model.listObjectValues( Item, self.rep_defined ) pattern = self.Model.findObjectValue( Item, self.rep_pattern ) bodydo = self.Model.findObjectValue( Item, self.rep_do ) bodyelse = self.Model.findObjectValue( Item, self.rep_else ) self.doIfAny( defined, pattern, bodydo, bodyelse, Variables ) elif cmd == self.rep_for: pattern = self.Model.findObjectValue( Item, self.rep_pattern ) bodyfirst = self.Model.findObjectValue( Item, self.rep_first ) bodydo = self.Model.findObjectValue( Item, self.rep_do ) bodylast = self.Model.findObjectValue( Item, self.rep_last ) bodysep = self.Model.findObjectValue( Item, self.rep_sep ) bodyelse = self.Model.findObjectValue( Item, self.rep_else ) self.doFor( pattern, bodyfirst, bodydo, bodylast, bodysep, bodyelse, Variables ) else: bodydo = self.Model.findObjectValue( Item, self.rep_do ) if bodydo: self.doBody( bodydo, Variables ) else: self.debug( "Unrecognized report command: Item "+ str(Item)+", cmd "+str(cmd) ) raise N3ReportError( "Unrecognized report command: "+ str(cmd) ) return def doOpen( self, chan, filename ): """ Open a named channel to write a specified file """ self.debug( "Open channel: chan="+chan+", filename="+filename ) if ( chan in self.Channels.keys() ) and self.Channels[chan]: raise N3ReportError( "Channel already open: "+chan ) self.Channels[chan] = open( filename, 'w' ) return def doClose( self, chan ): """ Close a named channel """ self.debug( "Close channel: chan="+chan ) if chan in self.Channels.keys(): if self.Channels[chan]: self.Channels[chan].close() del self.Channels[chan] return def doWrite( self, chan, data, Variables ): """ Write data to a named channel. The data to be written is derived from a sequence of values starting at the given node. Parameters: chan string containg channel name data formatter node to write Variables variable bindings to use """ if ( chan not in self.Channels.keys() ) or ( not self.Channels[chan] ): raise N3ReportError( "Channel not open: "+chan ) val = StringBuffer() self.loadBufferValue( data, Variables, val ) ### self.debug( "Write: "+val ) val.write( self.Channels[chan] ) return def doDebug( self, data, Variables ): """ Debug write data. The data to be written is derived from a value or sequence of values starting at the given node. Parameters: data formatter node to write """ val = self.findStringValue( data, Variables ) if not val: raise N3ReportError( "Invalid data value: "+str(item) ) self.debug( "Debug: "+val ) return def doIf( self, defined, pattern, bodydo, bodyelse, Variables ): """ If a given name is defined, and if a supplied pattern is matched in the database, consistent with current variable bindings, then perform a sequence of commands with the resulting variable bindings, otherwise execute an alternative sequence with the variable bindings so far. """ NewVars = None # Is name defined in current variable bindings? if not defined or Variables.value( defined.getString() ): if pattern: # Scan the pattern and build a query object. Query = self.makeQueryPattern( pattern, [] ) # See if query can be matched Query.init( Variables ) NewVars = Query.next() Query.close() else: # No pattern: execute body with current variables NewVars = Variables if NewVars: self.doBody( bodydo, NewVars ) elif bodyelse: self.doBody( bodyelse, Variables ) return def doIfAny( self, defined, pattern, bodydo, bodyelse, Variables ): """ If any of a given selection of names are defined, OR if the supplied pattern is matched in the database, consistent with current variable bindings, then perform a sequence of commands with the resulting variable bindings, otherwise execute an alternative sequence with the variable bindings so far. If any of the listed names are defined, no attempt is made to match the pattern. """ NewVars = None # Is name defined in current variable bindings? if defined: for n in defined: if Variables.value( n.getString() ): NewVars = Variables break if not NewVars and pattern: Query = self.makeQueryPattern( pattern, [] ) Query.init( Variables ) NewVars = Query.next() Query.close() if NewVars: self.doBody( bodydo, NewVars ) elif bodyelse: self.doBody( bodyelse, Variables ) return def doFor( self, pattern, bodyfirst, bodydo, bodylast, bodysep, bodyelse, Variables ): """ Repeat a supplied body of commands for each match for a supplied pattern in the database that is consistent with any existing variable bindings. The 'bodyfirst' sequence is executed first, and 'bodylast' last, if any match for the pattern is present. The 'bodysep' sequence is executed between each execution of 'bodydo' to generate separator output. If there are no matches for the pattern, the alternative 'bodyelse' path is executed with the original bindings. """ # Scan the pattern and build a query object. Query = self.makeQueryPattern( pattern, [] ) # Repeat for each query match Query.init( Variables ) NewVars = Query.next() if NewVars: self.doBody( bodyfirst, NewVars ) while NewVars: self.doBody( bodydo, NewVars ) NewVars = Query.next() if NewVars: self.doBody( bodysep, NewVars ) self.doBody( bodylast, Variables ) elif bodyelse: self.doBody( bodyelse, Variables ) Query.close() return #--------------------------------------------------------------------------- # Helper methods #--------------------------------------------------------------------------- # Construct a query pattern. # # Pattern is a reference to a list of values that make up the pattern # Tbuf is supplied with triple elements scanned so far # Qseq is supplied with the query pattern so far, and returned with the # complete pattern. # # The resulting query pattern is returned. # # Scan the pattern: each triple of nodes is used to make an N3QueryTriple # The resulting value is a sequence of N3QueryTriples # Triples from AND-branching constructs are concatenated to a single sequence # # The pattern defines a number of paths in the model graph, each # containing node URIs or literals that must be matched or variables # that must be matched if they are already bound, or which receive # new values corresponding to the graph node at that point in the # graph. # # A pattern is based on the following syntax, coded in the RDF graph: # # Pattern = "(" Path ")" # # Path = Subject Subpath # # Subpath = Predicate Object *( Subpath ) # | "(" Subpath *( "&" Subpath ) [ "|" Subpath ] ")" # # Subject = Variable | URI # # Predicate = Variable | URI # # Object = Variable | URI | Literal # # A path is represented as a list. # A subpath is represented as two lists: one for the elements that # must be matched concurrently (AND-match), and a separate list for # the pattern to match if the main path is not matched (ALT-match). # A variable to be matched or bound is represented as [ rep:var "name" ] # A URI to be matched is represented as [ rep:uri uriref ] # A literal to be matched is represented as [ rep:lit "string" ] # A number of simultaneously matched sub paths are represented as # [ rep:and path1, path2, ... ] or # [ rep:and path1 ; rep:and path2 ; ... ] # Ordering of simultaneously-matched paths is not important # An alternative path to be matched if the primary does not match # is represented as [ rep:alt path ] # # Two special values are recognized for RDF properties in a query: # rep:member matches any rdf:_n container membership property # rep:element matches any graph path of the form rdf:rest* rdf:first # These special property values can be used in a query to match all # of the members of a container or collection type respectively. # # Example pattern: # ( ?header # ( rdf:type hdr:HeaderField # & hdr:fieldName ?fname # & hdr:author ?author # ( hdr:name ?aname # & ( foaf:mbox ?mbox # | foaf:homepage ?homepage ) ) ) # # test:HdrPattern :- # ( [ rep:var "header" ] # [ rep:and # ( [ rep:uri rdf:type ] [ rep:uri hdr:HeaderField ] ), # ( [ rep:uri hdr:fieldName ] [ rep:var "fname" ] ), # ( [ rep:uri hdr:author ] [ rep:var "author" ] # [ rep:and # ( [ rep:uri hdr:name ] [ rep:var "aname" ] ), # ( [ rep:and # ( [ rep:uri foaf:mbox ] # [ rep:var "mbox" ] ) ; # rep:alt # ( [ rep:uri foaf:homepage ] # [ rep:var "homepage" ] ) # ] ) # ] ) # ] ) . # # Parameters: # Pattern reference to list node representing a pattern in the Model # Tbuf triple buffer: used to record triple values found so far; # when a full triple is assembled here, it is emitted. # # Returns: # A query pattern object corresponding to the pattern scanned from # the model (c.f. N3QueryTriple, N3QuerySequence, etc.) The key # methods of this object are: # q.init(Variables) # NewVars = q.next() # q.close() # def makeQueryPattern( self, Pattern, Tbuf ): # Look for pattern in cache (only if Tbuf is empty) PatternId = None if Pattern and not Tbuf: PatternId = Pattern.getIdent() if PatternId in self.PatternCache.keys(): return self.PatternCache[PatternId] # Not in cache: build it now ###debug### #self.debug( "makeQueryPattern: making "+Pattern.getIdent() ) #self.debug( "- Tbuf: "+`Tbuf` ) Qseq = [] Aseq = None while Pattern and ( Pattern != self.list_null ): item = self.Model.findObjectValue( Pattern, self.list_first ) Pattern = self.Model.findObjectValue( Pattern, self.list_rest ) if item == self.rep_member or item == self.rep_element: ###debug### #self.debug( "- member: "+str(item) ) self.addQueryNode( item, Tbuf, Qseq ) continue uri = self.Model.findObjectValue( item, self.rep_uri ) if uri: ###debug### #self.debug( "- uri: "+str(uri) ) self.addQueryNode( uri, Tbuf, Qseq ) continue lit = self.Model.findObjectValue( item, self.rep_lit ) if lit: ###debug### #self.debug( "- lit: "+str(lit) ) self.addQueryNode( lit, Tbuf, Qseq ) continue var = self.Model.findObjectValue( item, self.rep_var ) if var: ###debug### #self.debug( "- var: "+str(var) ) Qvar = N3Node( name=var.getString() ) self.addQueryNode( Qvar, Tbuf, Qseq ) continue # Check for rep:and pattern (with optional rep:alt) lst = self.Model.findStatements( item, self.rep_and, None ) if lst: ###debug### #self.debug( "- rep:and "+str(item) ) for s in lst: ###debug### #self.debug( "-- next: "+str(s) ) # NOTE: slicing makes copy of Tbuf Qpat = self.makeQueryPattern( s.getObject(), Tbuf[:] ) Qseq.append( Qpat ) alt = self.Model.findObjectValue( item, self.rep_alt ) if alt: ###debug### #self.debug( "- rep:alt "+str(alt) ) Apat = self.makeQueryPattern( alt, Tbuf[:] ) Aseq = [Apat] del Tbuf[1:3] continue # Check for rep:opt pattern # Like and, but with null alternative supplied lst = self.Model.findStatements( item, self.rep_opt, None ) if lst: ###debug### #self.debug( "- rep:opt "+str(lst) ) for s in lst: # NOTE: slicing makes copy of Tbuf Qpat = self.makeQueryPattern( s.getObject(), Tbuf[:] ) Qseq.append( Qpat ) Apat = N3QueryNull() Aseq = [Apat] del Tbuf[1:3] continue ###debug### #self.debug( "- Qseq: "+str(Qseq) ) #self.debug( "- Aseq: "+str(Aseq) ) # End of sequence: # check that Tbuf is empty (i.e. no half-finished triples) if len(Tbuf) > 1: self.debug( `Tbuf` ) raise N3ReportError( "Query pattern does not end with statement object" ) # Construct query pattern, save in cache, and return it p = N3QuerySequence( self.Model, Qseq, Aseq ) if PatternId: self.PatternCache[PatternId] = p return p # makeQueryPattern helper: Add a node to a query pattern. # # Add node to triple buffer. If the buffer contains 3 nodes, add a query triple # to the query sequence and reset the buffer to contain just the object of the # triple just processed. # def addQueryNode( self, Node, Tbuf, Qseq ): Tbuf.append( Node ) if len( Tbuf ) == 3: if Tbuf[1] == self.rep_member: # Container member query qt = N3QueryContainerMembers( self.Model, Tbuf[0], Tbuf[2] ) elif Tbuf[1] == self.rep_element: # List element query qt = N3QueryListElements( self.Model, Tbuf[0], Tbuf[2] ) else: # Ordinary triple query qt = N3QueryTriple( self.Model, Tbuf[0], Tbuf[1], Tbuf[2] ) Qseq.append( qt ) Tbuf[0] = Tbuf[2] del Tbuf[1:3] return # Construct a formatter object from a template in the graph. # # Possibile node values are: # "literal" # [ rep:var "name" ] # [ rep:local "name" ] # ( list of simple-values ) # rep:nl # rep:trimws # [ rep:tab "pos" ] # [ rep:tabsp "pos" ] # [ rep:tabnl "pos" ] # [ rep:left "pos" ] # [ rep:wrap "pos" ] # [ rep:indent "offset" ] # [ rep:if [defined "name"], ... ; # rep:do (template) ; # rep:else (template) ] # [ rep:ifany [defined "name"], ... ; # rep:do (template) ; # rep:else (template) ] # [ rep:defer (template) ] # [ rep:flush (template) ] # URI (display URI) # # A formatter is made up using the following classes; all have # a 'format()' method that accepts a variable binding set and an # output string value: # N3Formatter( FormatSeq ) # N3FormatString( StrVal ) # N3FormatVariable( StrVal, local=0 ) # N3FormatNewline() # N3FormatTab( Pos, Space=None, Newline=None ) # N3FormatMargins( Left=-1, Wrap=-1, Indent=0 ) # N3FormatIf( Defined, FormatT, FormatF, Any=0 ) # # Parameters: # Node is a graph node that describes the format template to # be loaded. # # Returns: # A Formatter object that will format a supplied variable binding # into a supplied buffer according to the formatting template defined # in the graph. (e.g. See N3Formatter.format() method.) # def makeFormatter( self, Node ): # Look for formatter template in cache FormatterId = None if Node: FormatterId = Node.getIdent() if FormatterId in self.FormatterCache.keys(): return self.FormatterCache[FormatterId] # Not in cache: build it now # Null node if not Node: #### self.debug( "Null formatter node" ) return N3FormatSequence( None ) # Literal if Node.isLiteral(): return N3FormatString( Node.getString() ) # newline if Node == self.rep_nl: return N3FormatNewline() # trim whitespace if Node == self.rep_trimws: return N3FormatTrim() # Variable var = self.Model.findObjectValue( Node, self.rep_var ) if var: return N3FormatVariable( var.getString() ) var = self.Model.findObjectValue( Node, self.rep_local ) if var: return N3FormatVariable( var.getString(), local=1 ) # List List = self.Model.findObjectValue( Node, self.list_first ) if List: FormatSeq = [] while Node and Node != self.list_null: Item = self.Model.findObjectValue( Node, self.list_first ) Node = self.Model.findObjectValue( Node, self.list_rest ) FormatSeq.append( self.makeFormatter( Item ) ) # Construct formatter template, save in cache, and return it f = N3FormatSequence( FormatSeq ) if FormatterId: self.FormatterCache[FormatterId] = f return f # Tab options tab = self.Model.findObjectValue( Node, self.rep_tab ) if tab: return N3FormatTab( int(tab.getString()) ) tabsp = self.Model.findObjectValue( Node, self.rep_tabsp ) if tabsp: return N3FormatTab( int(tabsp.getString()), Space=1 ) tabnl = self.Model.findObjectValue( Node, self.rep_tabnl ) if tabnl: return N3FormatTab( int(tabnl.getString()), Newline=1 ) # Margin setting left = self.Model.findObjectValue( Node, self.rep_left ) if left: return N3FormatMargins( Left=int(left.getString()) ) wrap = self.Model.findObjectValue( Node, self.rep_wrap ) if wrap: return N3FormatMargins( Wrap=int(wrap.getString()) ) indent = self.Model.findObjectValue( Node, self.rep_indent ) if indent: return N3FormatMargins( Indent=int(indent.getString()) ) # Conditionals ifall = self.Model.findObjectValue( Node, self.rep_if ) ifany = self.Model.findObjectValue( Node, self.rep_ifany ) if ifall or ifany: if ifany: iftest = self.rep_ifany else: iftest = self.rep_if dobody = self.Model.findObjectValue( Node, self.rep_do ) doelse = self.Model.findObjectValue( Node, self.rep_else ) nodes = self.Model.listPathValues( Node, (iftest,self.rep_defined) ) #### self.debug( "if: "+str(iftest) ) #### #### self.debug( "nodes: "+repr(nodes) ) #### names = [] for n in nodes: if n.isLiteral(): names.append( n.getString() ) else: self.debug( "Variable name expected: "+str(n) ) names.append( str(n) ) fBody = self.makeFormatter( dobody ) fElse = self.makeFormatter( doelse ) return N3FormatIf( names, fBody, fElse, Any=ifany ) # Deferred output defer = self.Model.findObjectValue( Node, self.rep_defer ) flush = self.Model.findObjectValue( Node, self.rep_flush ) if defer or flush: if defer: body, deferopt = defer, 0 else: body, deferopt = flush, 1 return N3FormatDefer( deferopt, self.makeFormatter( body ) ) # Other URI -- this test MUST come last! if Node.isURI(): return N3FormatString( Node.getIdent() ) # Unrecognized formatter node self.debug( "Unrecognized formatter node: "+str(Node) ) return N3FormatString( " [[[Format:" + Node.getString() + "]]] " ) # Determine the value associated with a node in a graph. # # (See makeFormatter() for more details.) # # Parameters: # Node the formatter template node whose value is returned # Variables Variable bindings to be used for evaluating the node data # Buff a StringBuffer object that receives the data described # by the node. # def loadBufferValue( self, Node, Variables, Buff ): Formatter = self.makeFormatter( Node ) Formatter.format( Variables, Buff ) return # Determine the value associated with a node in a graph. # # (See makeFormatter() for more details.) # # Parameters: # Node the formatter template node whose value is returned # Variables Variable bindings to be used for evaluating the node data # # Returns: # A string containing the value text # def findStringValue( self, Node, Variables ): Result = StringBuffer() self.loadBufferValue( Node, Variables, Result ) return str(Result) #--------------------------------------------------------------------------- # Debug output method # def debug( self, msg ): sys.stdout.write( "N3Report: "+msg+"\n" ) return # End of N3Report #--------+---------+---------+---------+---------+---------+---------+---------+ # # Stand-alone test code follows: # if __name__ == '__main__': # Prime the model data N3InputText = ''' @prefix rdf: . @prefix rdfs: . @prefix foaf: . @prefix hdr: . a hdr:HeaderField ; hdr:fieldName "Content-features" ; rdfs:label "Indicates content features of a MIME body part" ; hdr:protocol [ hdr:protocolName "mail" ; hdr:specification [ = ; hdr:document ] ] ; hdr:status "standards-track" ; hdr:author [ foaf:name "Graham Klyne" ; foaf:mbox ; foaf:organization "Clearswift Corporation" ; foaf:workplacePostal [ foaf:building "1310 Waterside" ; foaf:street1 "Arlington Business Park" ; foaf:street2 "Theale" ; foaf:alt1 "Alt1" ; foaf:alt2 "Alt2" ; foaf:city "Reading" ; foaf:area "Berks" ; foaf:postcode "RG7 4SA" ; foaf:country "UK" ] ; foaf:workplaceTel "011 8903 8903" ; foaf:workplaceFax "011 8903 9000" ; foaf:workplaceHomepage ] ; hdr:specification [ = ; hdr:document ; hdr:section "3" ] ; rdfs:comment """The 'Content-features:' header can be used to annotate a MIME body part with a media feature expression, to indicate features of the body part content. See also: RFC 2533, RFC 2506, RFC 2045.""" . a hdr:HeaderField ; hdr:fieldName "Content-alternate" ; rdfs:label "Indicates some alternative" ; hdr:protocol [ hdr:protocolName "mail" ; hdr:specification [ = ; hdr:document ] ] ; hdr:status "standards-track" ; hdr:author [ foaf:name "Graham Klyne" ; foaf:mbox ; foaf:organization "Clearswift Corporation" ; foaf:workplacePostal [ foaf:building "1310 Waterside " ; foaf:alt1 "Alt1 " ; foaf:alt2 "Alt2 " ; foaf:street3 "street3-here " ; foaf:city "Reading " ; foaf:area "Berks " ; foaf:country "UK" ] ; foaf:workplaceTel "011 8903 8903" ; foaf:workplaceFax "011 8903 9000" ; foaf:workplaceHomepage ] ; hdr:specification [ = ; hdr:document ; hdr:section "3" ] ; rdfs:comment """The 'Content-features:' header can be used to annotate a MIME body part with a media feature expression, to indicate features of the body part content. See also: RFC 2533, RFC 2506, RFC 2045.""" . a rdf:Seq ; rdf:_1 "item 1" ; rdfs:contains "item 2" ; rdf:_5 "item 3" . a hdr:List ; :- ( "item 1" "item 2" "item 3" ) . ''' N3ReportText = ''' @prefix rdf: . @prefix rdfs: . @prefix foaf: . @prefix hdr: . @prefix rep: . @prefix test: . test:Report a rep:Report ; :- ( [ rep:cmd rep:if ; rep:defined "file" ; rep:do ( [ rep:cmd rep:debug ; rep:data ("Writing file " "N3Report-" [ rep:var "file" ] ) ] [ rep:cmd rep:open ; rep:chan "0" ; rep:file ( "N3Report-" [ rep:var "file" ] ) ] [ rep:cmd rep:write ; rep:chan "0" ; rep:data test:ReportHead ] [ rep:cmd rep:for ; rep:pattern test:HdrPattern ; rep:first ( [ rep:cmd rep:write ; rep:chan "0" ; rep:data test:ReportItemFirst ] ) ; rep:do ( [ rep:cmd rep:write ; rep:chan "0" ; rep:data test:ReportItem ] ) ; rep:last ( [ rep:cmd rep:write ; rep:chan "0" ; rep:data test:ReportItemLast ] ) ; rep:sep ( [ rep:cmd rep:write ; rep:chan "0" ; rep:data test:Separator ] ) ; rep:else ( [ rep:cmd rep:write ; rep:chan "0" ; rep:data test:ReportEmpty ] ) ] [ rep:cmd rep:write ; rep:chan "0" ; rep:data test:ReportFoot ] [ rep:cmd rep:close ; rep:chan "0" ] ) ] ). test:PatternTests a rep:Report ; :- ( [ rep:cmd rep:if ; rep:defined "file" ; rep:do ( [ rep:cmd rep:debug ; rep:data ("Writing file " "N3Report-" [ rep:var "file" ] ) ] [ rep:cmd rep:open ; rep:chan "0" ; rep:file ( "N3Report-" [ rep:var "file" ] ) ] [ rep:cmd rep:for ; rep:pattern test:BindingMismatchPattern ; rep:do ( [ rep:cmd rep:write ; rep:chan "0" ; rep:data test:ReportMismatch ] ) ; rep:sep ( [ rep:cmd rep:write ; rep:chan "0" ; rep:data test:Separator ] ) ; rep:else ( [ rep:cmd rep:write ; rep:chan "0" ; rep:data test:ReportNoMismatch ] ) ] [ rep:cmd rep:for ; rep:pattern test:ContainerPattern ; rep:do ( [ rep:cmd rep:write ; rep:chan "0" ; rep:data test:ReportMember ] ) ; rep:sep ( [ rep:cmd rep:write ; rep:chan "0" ; rep:data test:Separator ] ) ; rep:else ( [ rep:cmd rep:write ; rep:chan "0" ; rep:data test:ReportNoMember ] ) ] [ rep:cmd rep:for ; rep:pattern test:ListPattern ; rep:do ( [ rep:cmd rep:write ; rep:chan "0" ; rep:data test:ReportMember ] ) ; rep:sep ( [ rep:cmd rep:write ; rep:chan "0" ; rep:data test:Separator ] ) ; rep:else ( [ rep:cmd rep:write ; rep:chan "0" ; rep:data test:ReportNoMember ] ) ] [ rep:cmd rep:close ; rep:chan "0" ] ) ] ). test:ReportHead :- ( "" rep:nl "" rep:nl "Table of header field registrations" rep:nl "" rep:nl "" rep:nl ) . test:HdrPattern :- ( [ rep:var "header" ] [ rep:and ( [ rep:uri rdf:type ] [ rep:uri hdr:HeaderField ] ), ( [ rep:uri hdr:fieldName ] [ rep:var "name" ] ), ( [ rep:uri rdfs:label ] [ rep:var "purpose" ] ), ( [ rep:uri hdr:protocol ] [ rep:var "p" ] [ rep:and ( [ rep:uri hdr:protocolName] [ rep:var "pname" ] ), ( [ rep:uri hdr:specification] [ rep:var "ps" ] [ rep:uri hdr:document ] [ rep:var "psdocument" ] ) ] ), ( [ rep:uri hdr:status ] [ rep:var "status" ] ), ( [ rep:uri hdr:author ] [ rep:var "a" ] [ rep:and ( [ rep:uri foaf:name ] [ rep:var "aname"] ), ( [ rep:uri foaf:mbox ] [ rep:var "ambox"] ), ( [ rep:uri foaf:organization ] [ rep:var "orgname"] ), ( [ rep:uri foaf:workplacePostal ] [ rep:var "wp"] [ rep:and ( [ rep:uri foaf:building ] [ rep:var "wpbuilding" ] ), ( [ rep:and ( [ rep:uri foaf:street1 ] [ rep:var "wpstreet1" ] ) ; rep:alt ( [ rep:uri foaf:alt1 ] [ rep:var "wpstreet2" ] ) ] ), ( [ rep:and ( [ rep:uri foaf:street2 ] [ rep:var "wpstreet2" ] ) ; rep:alt ( [ rep:uri foaf:alt2 ] [ rep:var "wpstreet1" ] ) ] ), ( [ rep:and ( [ rep:uri foaf:street3 ] [ rep:var "wpstreet3" ] ) ; rep:alt () ] ), ( [ rep:uri foaf:city ] [ rep:var "wpcity" ] ), ( [ rep:opt ( [ rep:uri foaf:area ] [ rep:var "wparea" ] ) ] ), ( [ rep:opt ( [ rep:uri foaf:postcode ] [ rep:var "wppostcode" ] ) ] ), ( [ rep:uri foaf:country ] [ rep:var "wpcountry" ] ) ] ), ( [ rep:uri foaf:workplaceTel ] [ rep:var "wtel"] ), ( [ rep:uri foaf:workplaceFax ] [ rep:var "wfax"] ), ( [ rep:uri foaf:workplaceHomepage ] [ rep:var "wurl"] ) ] ), ( [ rep:uri hdr:specification ] [ rep:var "hs" ] [ rep:and ( [ rep:uri hdr:document ] [ rep:var "hsdocument" ] ), ( [ rep:uri hdr:section ] [ rep:var "hssection" ] ) ] ), ( [ rep:uri rdfs:comment ] [ rep:var "info" ] ) ] ) . test:ReportItemFirst :- ( "

Start of list

" ) . test:ReportItem :- ( "

Header field: " [rep:var "name"] "

" [ rep:indent "+4" ] [ rep:tabnl "4" ] [ rep:tabsp "4" ] "

" [rep:var "purpose"] "

" rep:nl "
" rep:nl "
Applicable protocol:
" [rep:var "pname"] " (" "" [rep:var "psdocument"] "" ")
" rep:nl "
Status:
" [rep:var "status"] "
" rep:nl "
Author/Change controller:
" [ rep:indent "+4" ] rep:nl "
" rep:nl [rep:var "aname"] " (" [rep:var "ambox"] ")
" rep:nl [rep:var "orgname"] "
" rep:nl [rep:var "wpbuilding"] ", " rep:nl [rep:var "wpstreet1" ] ", " [rep:var "wpstreet2" ] ", " [rep:if [rep:defined "wpstreet3"] ; rep:do ( ">>> here: " [rep:var "wpstreet3" ] [rep:defer ", "] ) ; rep:else ( "Not here: " [rep:var "wpstreet3" ] [rep:defer "! "] ) ] [rep:ifany [rep:defined "wpstreet1"], [rep:defined "wpstreet2"] ; rep:do ( ",*any*
" rep:nl ) ] [rep:var "wpcity" ] rep:trimws ", " [rep:var "wparea" ] rep:trimws ", " [rep:var "wppostcode"] rep:trimws ", " [rep:var "wpcountry" ] [rep:defer "[[[should not appear]]]"] [rep:flush ( ".
" rep:nl ) ] "Tel: " [rep:var "wtel"] "
" rep:nl "Fax: " [rep:var "wfax"] "
" rep:nl "URL: " "" [rep:var "wurl"] "
" rep:nl "
" [ rep:indent "-4" ] rep:nl "
Specification document(s):
" rep:nl "
" "" [ rep:var "hsdocument" ] "" " (section " [ rep:var "hssection" ] ")" "
" rep:nl "
Related information:
" [ rep:indent "+4" ] rep:nl "
" [ rep:var "info" ] "
" [ rep:indent "-4" ] rep:nl "
" [ rep:indent "-4" ] rep:nl ) . test:ReportItemLast :- ( "

End of list

" ) . test:Separator :- ( "*** separator ***
" rep:nl ) . test:ReportEmpty :- ( "Header field not found" rep:nl ) . test:ReportFoot :- ( "" rep:nl "" rep:nl ) . test:BindingMismatchPattern :- ( [ rep:var "header1" ] [ rep:and ( [ rep:uri rdf:type ] [ rep:uri hdr:HeaderField ] ), ( [ rep:uri hdr:fieldName ] [ rep:var "header1" ] ) ] ) . test:ReportMismatch :- ( "Binding name mismatch, header=" [ rep:var "header1" ] rep:nl ) . test:ReportNoMismatch :- ( "Binding name mismatch not selected" rep:nl ) . test:ContainerPattern :- ( [ rep:var "container" ] rep:member [ rep:var "member" ] ) . test:ListPattern :- ( [ rep:var "container" ] [ rep:and ( [rep:uri rdf:type] [rep:uri hdr:List] ), ( rep:element [ rep:var "member" ] ) ] ) . test:ReportMember :- ( "Container " [ rep:local "container" ] ", member '" [ rep:var "member" ] "'" rep:nl ) . test:ReportNoMember :- ( "No container member selected" rep:nl ) . ''' sys.stdout.write( "Create model\n" ) InputStr = StringIO.StringIO( N3InputText ) xModel = N3Model( InputStr ) if not xModel: sys.stdout.write( "Failed to create data model\n" ) raise N3ReportError( "Test abandoned" ) ReportStr = StringIO.StringIO( N3ReportText ) ReportMod = N3Model( ReportStr ) if not xModel: sys.stdout.write( "Failed to create report model\n" ) raise N3ReportError( "Test abandoned" ) xModel.merge( ReportMod ) sys.stdout.write( "Create report object\n" ) test_Report = xModel.makeNode( "test:Report" ) # test_Report = xModel.makeNode( "test:PatternTests" ) Report = N3Report( xModel, test_Report ) sys.stdout.write( "Generate report\n" ) Report.generate( { "file": N3Node( lit="TestReport.html" ) } ) sys.stdout.write( "Exiting\n" ) #--------+---------+---------+---------+---------+---------+---------+---------+ # # $Log: N3Report.py,v $ # Revision 1.28 2004/08/16 12:56:00 graham # Removed default (empty) prefix definition in N3Model, which was # causing # the report generator to fail on input files containing a null @prefix declaration. # Be aware that this fix might cause some failures in other places that should # be fixed rather than backing out this change - having a default empty prefix # like this was a bad idea. # # Revision 1.27 2004/03/26 21:26:23 graham # Bug-fixes in report generation software to support new header # field registry document generation. Also added some generic # variable binding options to N3GenReport. # # Revision 1.26 2002/10/21 16:23:18 graham # Add options to include leading and trailing text around repeated entries. # # Revision 1.25 2002/09/19 15:07:25 graham # Updated document issue report generator to support document details, # document history, issue histories, and tidy up output. # # Revision 1.24 2002/09/19 09:36:56 graham # Added facility to scan contents of list (using rdf:first, rdf:rest) # # Revision 1.23 2002/09/18 17:29:49 graham # Added facility to scan contents of container # # Revision 1.21 2002/06/05 15:26:33 graham # Option to trim spaces from formatted values # # Revision 1.20 2002/05/21 21:25:07 graham # Added separator feature to repeated patterns # # Revision 1.19 2002/05/21 20:50:36 graham # Fix error in XML output when comments missing # Allow multiple authors for header field # # Revision 1.18 2002/05/14 15:18:44 graham # Initial verion of N3ModelJenaMem.py, which # works with a Jena memory-based RDF store # # Revision 1.17 2002/05/10 11:08:19 graham # Added deferred output features to handle list separators # # Revision 1.16 2002/05/09 16:06:18 graham # Add query pattern and formatter template caching # # Revision 1.14 2002/05/07 10:54:16 graham # Fixes to accept N3 output: options to control default prefix table, accept 'this' as node name (treated as <#>), allow '-' in qnames (not strictly valid N3?) # # Revision 1.13 2002/05/03 14:49:55 graham # Added conditional and alternative features to query pattern and formatter templates # # Revision 1.12 2002/05/01 15:43:48 graham # Cleanup small details # # Revision 1.11 2002/05/01 14:23:55 graham # Add layout control options # # Revision 1.10 2002/05/01 11:36:58 graham # Add rep:debug option # # Revision 1.9 2002/05/01 11:15:33 graham # Added option to test for defined name # # Revision 1.8 2002/04/28 17:01:45 graham # Partial port to Jython-21 (but having problems with os module support) # # Revision 1.7 2002/04/25 19:00:13 graham # Update CVS keyword fields # # Revision 1.6 2002/04/25 18:53:25 graham # Add copyright and disclaimer notices # # Revision 1.5 2002/04/24 20:21:12 graham # Added notes for further developments # # Revision 1.4 2002/04/24 17:59:00 graham # Message registry generator working # # Revision 1.3 2002/04/24 14:36:33 graham # Save edits # # Revision 1.2 2002/04/24 10:28:01 graham # Improved report generation # # Revision 1.1 2002/04/24 09:51:26 graham # Basic query and report generation works # #