source: CMIP6dreq/trunk/src/dreq.py @ 390

Subversion URL: http://proj.badc.rl.ac.uk/svn/exarch/CMIP6dreq/trunk/src/dreq.py@390
Revision 390, 19.5 KB checked in by mjuckes, 5 years ago (diff)

For 2nd distribution; example volume estimate added

Line 
1"""This module provides a basic python API to the Data Request.
2After ingesting the XML documents (configuration and request) the module generates two python objects:
31. A collection of records
42. Index
5"""
6import xml, string, collections
7import xml.dom
8import xml.dom.minidom
9import re, shelve
10import sets
11
12class rechecks(object):
13  def __init__(self):
14    self.__isInt = re.compile( '-{0,1}[0-9]+' )
15
16  def isIntStr( self, tv ):
17    if type( tv ) not in {type(''),type(u'')}:
18      self.reason = 'NOT STRING'
19      return False
20    ok = self.__isInt.match( tv ) != None
21    if not ok:
22      self.reason = 'Failed to match regular expression for integers'
23    else:
24      self.reason = ''
25    return ok
26
27class dreqItemBase(object):
28       __doc__ = """A base class used in the definition of records. Designed to be used via a class factory which sets "itemLabelMode" and "attributes" before the class is instantiated: attempting to instantiate the class before setting these will trigger an exception."""
29       _indexInitialised = False
30       _inx = None
31       _urlBase = ''
32       _htmlStyle = {}
33
34       def __init__(self,dict=None,xmlMiniDom=None,id='defaultId',etree=False):
35         dictMode = dict != None
36         mdMode = xmlMiniDom != None
37         assert not( dictMode and mdMode), 'Mode must be either dictionary of minidom: both assigned'
38         assert dictMode or mdMode, 'Mode must be either dictionary of minidom: neither assigned'
39         ##self._defaults = { }
40         ##self._globalDefault = '__unset__'
41         self._contentInitialised = False
42         if dictMode:
43           self.dictInit( dict )
44         elif mdMode:
45           self.mdInit( xmlMiniDom, etree=etree )
46
47       def __repr__(self):
48         """Provide a one line summary of identifying the object."""
49         if self._contentInitialised:
50           return 'Item <%s>: [%s] %s' % (self._h.title,self.label,self.title)
51         else:
52           return 'Item <%s>: uninitialised' % self._h.title
53
54       def __info__(self,full=False):
55         """Print a summary of the data held in the object as a list of key/value pairs"""
56         if self._contentInitialised:
57           print 'Item <%s>: [%s] %s' % (self._h.title,self.label,self.title)
58           for a in self.__dict__.keys():
59             if a[0] != '_' or full:
60               if self._a[a].rClass == 'internalLink' and self._base._indexInitialised:
61                 targ = self._base._inx.uid[ self.__dict__[a] ][1]
62                 print '   %s: [%s]%s [%s]' % ( a, targ._h.label, targ.label, self.__dict__[a] )
63               else:
64                 print '    %s: %s' % ( a, self.__dict__[a] )
65         else:
66           print 'Item <%s>: uninitialised' % self.sectionLabel
67
68       def __href__(self,odir=""):
69         igns =  {'','__unset__'}
70         if self.__dict__.has_key( 'description' ) and string.strip( self.description ) not in igns:
71           ttl = self.description
72         elif self.__dict__.has_key( 'title' ) and string.strip( self.title ) not in igns:
73           ttl = self.title
74         else:
75           ttl = self.label
76         return '<span title="%s"><a href="%s%s.html">%s</a></span>' % (ttl,odir,self.uid,self.uid)
77
78       def __html__(self):
79         """Create html view"""
80         msg = []
81         if self._contentInitialised:
82           sect = self._h.label
83           msg.append( '<h1>%s: [%s] %s</h1>' % (self._h.title,self.label,self.title) )
84           msg.append( '<a href="../index.html">Home</a> &rarr; <a href="../index/%s.html">%s section index</a><br/>\n' % (sect, self._h.title) )
85           msg.append( '<ul>' )
86           for a in self.__dict__.keys():
87             if a[0] != '_':
88               if self._a[a].rClass == 'internalLink' and self._base._indexInitialised:
89                 if self.__dict__[a] == '__unset__':
90                   m = '<li>%s: %s [missing link]</li>' % ( a, self.__dict__[a] )
91                 else:
92                   targ = self._base._inx.uid[ self.__dict__[a] ][1]
93                   m = '<li>%s: [%s] %s [%s]</li>' % ( a, targ._h.label, targ.label, targ.__href__() )
94               else:
95                 m = '<li>%s: %s</li>' % ( a, self.__dict__[a] )
96               msg.append( m )
97           msg.append( '</ul>' )
98##
99## add list of inward references
100##
101           if self._base._indexInitialised:
102             f1 = self._htmlStyle.get( sect, {} ).get( 'getIrefs', None ) != None
103             if f1:
104               tl = []
105               if f1:
106                 tl = self._htmlStyle[sect]['getIrefs']
107               doall = '__all__' in tl
108               if doall:
109                 tl = self._inx.iref_by_sect[self.uid].a.keys()
110               am = []
111               for t in tl:
112                 if self._inx.iref_by_sect[self.uid].a.has_key(t):
113                   am.append( '<h3>%s</h3>' % t )
114                   am.append( '<ul>' )
115                   items = [self._inx.uid[u][1] for  u in self._inx.iref_by_sect[self.uid].a[t] ]
116                   items.sort( ds('label').cmp )
117                   for targ in items:
118                     m = '<li>%s:%s [%s]</li>' % ( targ._h.label, targ.label, targ.__href__() )
119                     am.append( m )
120                   am.append( '</ul>' )
121               if len(am) > 0:
122                  msg.append( '<h2>Links from other sections</h2>' )
123                  for m in am:
124                    msg.append(m)
125               
126         else:
127           msg.append( '<b>Item %s: uninitialised</b>' % self.sectionLabel )
128         return msg
129
130
131       def dictInit( self, dict ):
132         __doc__ = """Initialise from a dictionary."""
133         for a in self._a.keys():
134           if dict.has_key(a):
135             self.__dict__[a] = dict[a]
136           else:
137             self.__dict__[a] = self._d.defaults.get( a, self._d.glob )
138         self._contentInitialised = True
139
140       def mdInit( self, el, etree=False ):
141         __doc__ = """Initialisation from a mindom XML element. The list of attributes must be set by the class factory before the class is initialised"""
142         deferredHandling=False
143         nw1 = 0
144         tvtl = []
145         if etree:
146           ks = sets.Set( el.keys() )
147           for a in self._a.keys():
148             if a in ks:
149               aa = '%s%s' % (self.ns,a)
150               tvtl.append( (a,True, str( el.get( a ) ) ) )
151             else:
152               tvtl.append( (a,False,None) )
153         else:
154           for a in self._a.keys():
155             if el.hasAttribute( a ):
156               tvtl.append( (a,True, str( el.getAttribute( a ) ) ) )
157             else:
158               tvtl.append( (a,False,None) )
159       
160         for a,tv,v in tvtl:
161           if tv:
162             if self._a[a].type == u'xs:integer':
163               if self._rc.isIntStr( v ):
164                 v = int(v)
165               else:
166                 v = string.strip(v)
167                 thissect = '%s [%s]' % (self._h.title,self._h.tag)
168                 if v in { '',u'',' ', u' '}:
169                   if nw1 < 20:
170                     print 'WARN.050.0001: input integer non-compliant: %s: %s: "%s" -- set to zero' % (thissect,a,v)
171                     nw1 += 1
172                   v = 0
173                 else:
174                   try:
175                     v = int(float(v))
176                     print 'WARN: input integer non-compliant: %s: %s: %s' % (thissect,a,v)
177                   except:
178                     msg = 'ERROR: failed to convert integer: %s: %s: %s' % (thissect,a,v)
179                     deferredHandling=True
180             elif self._a[a].type == u'xs:boolean':
181               v = v in {'true','1'}
182             self.__dict__[a] = v
183           else:
184             if a in {'uid'}:
185               thissect = '%s [%s]' % (self._h.title,self._h.tag)
186               print 'ERROR.020.0001: missing uid: %s' % thissect
187               if etree:
188                 print ks
189                 import sys
190                 sys.exit(0)
191             self.__dict__[a] = self._d.defaults.get( a, self._d.glob )
192
193           ##if type( self.__dict__.get( 'rowIndex', 0 ) ) != type(0):
194             ##print 'Bad row index ', el.hasAttribute( 'rowIndex' )
195             ##raise
196           if deferredHandling:
197             print msg
198
199         self._contentInitialised = True
200
201   
202class config(object):
203  """Read in a vocabulary collection configuration document and a vocabulary document"""
204
205  def __init__(self, configdoc='out/dreqDefn.xml', thisdoc='../workbook/trial_20150724.xml', useShelve=False):
206    self.rc = rechecks()
207    self.silent = True
208    self.vdef = configdoc
209    self.vsamp = thisdoc
210    self.nts = collections.namedtuple( 'sectdef', ['tag','label','title','id','itemLabelMode','level'] )
211    self.nti = collections.namedtuple( 'itemdef', ['tag','label','title','type','rClass','techNote'] )
212    self.ntt = collections.namedtuple( 'sectinit', ['header','attributes','defaults'] )
213    self.nt__default = collections.namedtuple( 'deflt', ['defaults','glob'] )
214    self.ntf = collections.namedtuple( 'sect', ['header','attDefn','items'] )
215
216    self.coll = {}
217    doc = xml.dom.minidom.parse( self.vdef  )
218##
219## elementTree parsing implemented for main document
220##
221    self.etree = False
222    self.etree = True
223    if self.etree:
224      import xml.etree.cElementTree as cel
225
226      self.contentDoc = cel.parse( self.vsamp )
227      root = self.contentDoc.getroot()
228      bs = string.split( root.tag, '}' )
229      if len( bs ) > 1:
230        self.ns = bs[0] + '}'
231      else:
232        self.ns = None
233    else:
234      self.contentDoc = xml.dom.minidom.parse( self.vsamp )
235      self.ns = None
236
237    vl = doc.getElementsByTagName( 'table' )
238    self.tables = {}
239    tables = {}
240    self.tableClasses = {}
241    self.tableItems = collections.defaultdict( list )
242    for v in vl:
243      t = self.parsevcfg(v)
244      tables[t[0].label] = t
245      self.tableClasses[t[0].label] = self.itemClassFact( t, ns=self.ns )
246
247
248    self.recordAttributeDefn = tables
249    for k in tables.keys():
250      if self.etree:
251        vl = root.findall( './/%s%s' % (self.ns,k) )
252        if len(vl) == 1:
253          v = vl[0]
254          t = v.get( 'title' )
255          i = v.get( 'id' )
256          il = v.findall( '%sitem' % self.ns )
257          self.info( '%s, %s, %s, %s' % ( k, t, i, len(il) ) )
258 
259          self.tables[k] = (i,t,len(il))
260       
261          for i in il:
262            ii = self.tableClasses[k](xmlMiniDom=i, etree=True)
263            self.tableItems[k].append( ii )
264        elif len(vl) > 1:
265          assert False, 'not able to handle repeat sections with etree yet'
266      else:
267        vl = self.contentDoc.getElementsByTagName( k )
268        if len(vl) == 1:
269          v = vl[0]
270          t = v.getAttribute( 'title' )
271          i = v.getAttribute( 'id' )
272          il = v.getElementsByTagName( 'item' )
273          self.info( '%s, %s, %s, %s' % ( k, t, i, len(il) ) )
274 
275          self.tables[k] = (i,t,len(il))
276       
277          for i in il:
278            ii = self.tableClasses[k](xmlMiniDom=i)
279            self.tableItems[k].append( ii )
280        elif len(vl) > 1:
281          l1 = []
282          l2 = []
283          for v in vl:
284            t = v.getAttribute( 'title' )
285            i = v.getAttribute( 'id' )
286            il = v.getElementsByTagName( 'item' )
287            self.info( '%s, %s, %s, %s' % ( k, t, i, len(il) ) )
288            l1.append( (i,t,len(il)) )
289         
290            l2i = []
291            for i in il:
292              ii = self.tableClasses[k](xmlMiniDom=i)
293              l2i.append( ii )
294            l2.append( l2i )
295          self.tables[k] = l1
296          self.tableItems[k] = l2
297      self.coll[k] = self.ntf( self.recordAttributeDefn[k].header, self.recordAttributeDefn[k].attributes, self.tableItems[k] )
298 
299  def info(self,ss):
300    if not self.silent:
301      print ss
302
303  def get(self):
304    return self.coll
305
306  def itemClassFact(self, sectionInfo,ns=None):
307     class dreqItem(dreqItemBase):
308       """Inherits all methods from dreqItemBase.
309
310USAGE
311-----
312The instanstiated object contains a single data record. The "_h" attribute links to information about the record and the section it belongs to.
313
314object._a: a python dictionary defining the attributes in each record. The keys in the dictionary correspond to the attribute names and the values are python "named tuples" (from the "collections" module). E.g. object._a['priority'].type contains the type of the 'priority' attribute. Type is expressed using XSD schema language, so "xs:integer" implies integer.  The "rClass" attribute carries information about usage. If object._a['xxx'].rClass = u'internalLink' then the record attribute provides a link to another element and object.xxx is the unique identifier of that element.
315
316object._h: a python named tuple describing the section. E.g. object.parent.header.title is the section title (E.g. "CMOR Variables")
317"""
318       _base=dreqItemBase
319       
320     dreqItem._h = sectionInfo.header
321     dreqItem._a = sectionInfo.attributes
322     dreqItem._d = sectionInfo.defaults
323     ##dreqItem.itemLabelMode = itemLabelMode
324     ##dreqItem.attributes = attributes
325     dreqItem._rc = self.rc
326     dreqItem.ns = ns
327     return dreqItem
328         
329  def parsevcfg(self,v):
330      """Parse a section definition element, including all the record attributes. The results are returned as a namedtuple of attributes for the section and a dictionary of record attribute specifications."""
331      l = v.getAttribute( 'label' )
332      t = v.getAttribute( 'title' )
333      i = v.getAttribute( 'id' )
334      ilm = v.getAttribute( 'itemLabelMode' )
335      lev = v.getAttribute( 'level' )
336      il = v.getElementsByTagName( 'rowAttribute' )
337      vtt = self.nts( v.nodeName, l,t,i,ilm,lev )
338      idict = {}
339      for i in il:
340        tt = self.parseicfg(i)
341        idict[tt.label] = tt
342      deflt = self.nt__default( {}, '__unset__' )
343      return self.ntt( vtt, idict, deflt )
344
345  def parseicfg(self,i):
346      """Parse a record attribute specification"""
347      defs = {'type':"xs:string"}
348      ll = []
349      for k in ['label','title','type','class','techNote']:
350        if i.hasAttribute( k ):
351          ll.append( i.getAttribute( k ) )
352        else:
353          ll.append( defs.get( k, None ) )
354      l, t, ty, cls, tn = ll
355      self.lastTitle = t
356      return self.nti( i.nodeName, l,t,ty,cls,tn )
357
358class container(object):
359  """Simple container class, to hold a set of dictionaries of lists."""
360  def __init__(self, atl ):
361    self.uid = {}
362    for a in atl:
363      self.__dict__[a] =  collections.defaultdict( list )
364
365class c1(object):
366  def __init__(self):
367    self.a = collections.defaultdict( list )
368class index(object):
369  """Create an index of the document. Cross-references are generated from attributes with class 'internalLink'.
370This version assumes that each record is identified by an "uid" attribute and that there is a "var" section.
371Invalid internal links are recorded in tme "missingIds" dictionary.
372For any record, with identifier u, iref_by_uid[u] gives a list of the section and identifier of records linking to that record.
373"""
374
375  def __init__(self, dreq):
376    self.silent = True
377    self.uid = {}
378    self.uid2 = collections.defaultdict( list )
379    nativeAtts = ['uid','iref_by_uid','iref_by_sect','missingIds']
380    naok = map( lambda x: not dreq.has_key(x), nativeAtts )
381    assert all(naok), 'This version cannot index collections containing sections with names: %s' % str( nativeAtts )
382    self.var_uid = {}
383    self.var_by_name = collections.defaultdict( list )
384    self.var_by_sn = collections.defaultdict( list )
385    self.iref_by_uid = collections.defaultdict( list )
386    irefdict = collections.defaultdict( list )
387    for k in dreq.keys():
388      if dreq[k].attDefn.has_key('sn'):
389         self.__dict__[k] =  container( ['label','sn'] )
390      else:
391         self.__dict__[k] =  container( ['label'] )
392    ##
393    ## collected names of attributes which carry internal links
394    ##
395      for ka in dreq[k].attDefn.keys():
396        if dreq[k].attDefn[ka].rClass == 'internalLink':
397           irefdict[k].append( ka )
398
399    for k in dreq.keys():
400        for i in dreq[k].items:
401          assert i.__dict__.has_key('uid'), 'uid not found::\n%s\n%s' % (str(i._h),str(i.__dict__) )
402          if self.uid.has_key(i.uid):
403            print 'ERROR.100.0001: Duplicate uid: %s [%s]' % (i.uid,i._h.title)
404            self.uid2[i.uid].append( (k,i) )
405          else:
406            self.uid[i.uid] = (k,i)
407
408    self.missingIds = collections.defaultdict( list )
409    self.iref_by_sect = collections.defaultdict( c1 )
410    for k in dreq.keys():
411        for k2 in irefdict.get( k, [] ):
412          n1 = 0
413          n2 = 0
414          for i in dreq[k].items:
415            id2 = i.__dict__.get( k2 )
416            if id2 != '__unset__':
417              sect = i._h.label
418              self.iref_by_uid[ id2 ].append( (sect,i.uid) )
419              self.iref_by_sect[ id2 ].a[sect].append( i.uid )
420              if self.uid.has_key( id2 ):
421                n1 += 1
422              else:
423                n2 += 1
424                self.missingIds[id2].append( (k,k2,i.uid) )
425          self.info(  'INFO:: %s, %s%s (%s)' % (k,k2,n1,n2) )
426
427    for k in dreq.keys():
428      for i in dreq[k].items:
429        self.__dict__[k].uid[i.uid] = i
430        self.__dict__[k].label[i.label].append( i.uid )
431        if dreq[k].attDefn.has_key('sn'):
432          self.__dict__[k].sn[i.sn].append( i.uid )
433
434  def info(self,ss):
435    if not self.silent:
436      print ss
437
438class ds(object):
439  def __init__(self,k):
440    self.k = k
441  def cmp(self,x,y):
442    return cmp( x.__dict__[self.k], y.__dict__[self.k] )
443
444src1 = '../workbook/trial_20150831.xml'
445class loadDreq(object):
446  def __init__(self,dreqXML='../docs/dreq.xml',configdoc='../docs/dreq2Defn.xml', useShelve=False ):
447    self.c = config( thisdoc=dreqXML, configdoc=configdoc, useShelve=useShelve)
448    self.coll = self.c.get()
449    self.inx = index(self.coll)
450##
451## add index to Item base class .. so that it can be accessed by item instances
452##
453    dreqItemBase._inx = self.inx
454    dreqItemBase._indexInitialised = True
455    dreqItemBase._htmlStyle['CMORvar'] = {'getIrefs':['requestVar']}
456    dreqItemBase._htmlStyle['requestVarGroup'] = {'getIrefs':['requestVar','requestLink']}
457    dreqItemBase._htmlStyle['var'] = {'getIrefs':['CMORvar']}
458    dreqItemBase._htmlStyle['objective'] = {'getIrefs':['objectiveLink']}
459    dreqItemBase._htmlStyle['requestLink'] = {'getIrefs':['objectiveLink','requestItem']}
460    dreqItemBase._htmlStyle['exptgroup'] = {'getIrefs':['experiment']}
461    dreqItemBase._htmlStyle['remarks'] = {'getIrefs':['__all__']}
462##    dreqItemBase._htmlStyle['__general__'] = {'addRemarks':True}
463
464    self.pageTmpl = """<html><head><title>%s</title></head><body>%s</body></html>"""
465
466  def makeHtml(self,odir='./html'):
467    for k in self.inx.uid.keys():
468      i = self.inx.uid[k][1]
469      ttl = 'Data Request Record: [%s]%s' % (i._h.label,i.label)
470      bdy = string.join( i.__html__( ), '\n' )
471      oo = open( '%s/u/%s.html' % (odir,i.uid), 'w' )
472      oo.write( self.pageTmpl % (ttl, bdy ) )
473      oo.close()
474
475    ttl0 = 'Data Request Index'
476    msg0 = ['<h1>%s</h1>' % ttl0, '<ul>',]
477    ks = sorted( self.coll.keys() )
478    for k in ks:
479##
480## sort on item label
481##
482      self.coll[k].items.sort( ds('label').cmp )
483      ttl = 'Data Request Section: %s' % k
484      msg0.append( '<li><a href="index/%s.html">%s [%s]</a></li>\n' % (k,self.coll[k].header.title,k) )
485      msg = ['<h1>%s</h1>\n' % ttl, '<ul>',]
486      msg.append( '<a href="../index.html">Home</a><br/>\n' )
487      for i in self.coll[k].items:
488        m = '<li>%s: %s</li>' % ( i.label, i.__href__(odir='../u/') )
489        msg.append( m )
490      msg.append( '</ul>' )
491      bdy = string.join( msg, '\n' )
492      oo = open( '%s/index/%s.html' % (odir,k), 'w' )
493      oo.write( self.pageTmpl % (ttl, bdy ) )
494      oo.close()
495    msg0.append( '</ul>' )
496    bdy = string.join( msg0, '\n' )
497    oo = open( '%s/index.html' % odir, 'w' )
498    oo.write( self.pageTmpl % (ttl, bdy ) )
499    oo.close()
500   
501if __name__ == '__main__':
502  dreq = loadDreq( )
503
Note: See TracBrowser for help on using the repository browser.