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

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

Moved to dreq2..

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