source: CMIP6dreq/trunk/dreqPy/dreq.py @ 460

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

Updated request

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