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

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

new distrib

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, os, sys
10try:
11  from __init__ import DOC_DIR, version
12except:
13  from dreqPy.__init__ import DOC_DIR, version
14
15python2 = True
16if sys.version_info[0] == 3:
17  python2 = False
18
19jsh='''<link type="text/css" href="/css/jquery-ui-1.8.16.custom.css" rel="Stylesheet" />
20 <script src="/js/2013/jquery.min.js" type="text/javascript"></script>
21 <script src="/js/2013/jquery-ui.min.js" type="text/javascript"></script>
22 <script src="/js/2013/jquery.cookie.js" type="text/javascript"></script>
23
24<link type="text/css" href="/css/dreq.css" rel="Stylesheet" />
25'''
26
27blockSchemaFile = '%s/%s' % (DOC_DIR, 'BlockSchema.csv' )
28
29class lutilsC(object):
30
31  def __init__(self):
32    pass
33
34  def addAttributes( self, thisClass, attrDict ):
35    """Add a set of attributes, from a dictionary, to a class"""
36    for k in attrDict:
37      setattr( thisClass, '%s' % k , attrDict[k] )
38
39  def itemClassFact(self, sectionInfo,ns=None):
40     class dreqItem(dreqItemBase):
41       """Inherits all methods from dreqItemBase.
42
43USAGE
44-----
45The instanstiated object contains a single data record. The "_h" attribute links to information about the record and the section it belongs to.
46
47object._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.
48
49object._h: a python named tuple describing the section. E.g. object._h.title is the section title (E.g. "CMOR Variables")
50"""
51       _base=dreqItemBase
52       
53     dreqItem.__name__ = 'dreqItem_%s' % str( sectionInfo.header.label )
54     dreqItem._h = sectionInfo.header
55     dreqItem._a = sectionInfo.attributes
56     dreqItem._d = sectionInfo.defaults
57     if sectionInfo.attributes != None:
58        self.addAttributes(dreqItem, sectionInfo.attributes )
59     ##dreqItem.itemLabelMode = itemLabelMode
60     ##dreqItem.attributes = attributes
61     dreqItem._rc = rechecks()
62     dreqItem.ns = ns
63     return dreqItem
64
65         
66
67##
68## from http://stackoverflow.com/questions/4474754/how-to-keep-comments-while-parsing-xml-using-python-elementtree
69##
70## does not work for python3 ... may not be needed.
71##
72## needed in python2.6 to preserve comments in output XML
73##
74def getParser():
75  import xml.etree.ElementTree as ET
76  class PCParser(ET.XMLTreeBuilder):
77
78     def __init__(self):
79       ET.XMLTreeBuilder.__init__(self)
80       # assumes ElementTree 1.2.X
81       self._parser.CommentHandler = self.handle_comment
82
83     def handle_comment(self, data):
84       self._target.start(ET.Comment, {})
85       self._target.data(data)
86       self._target.end(ET.Comment)
87
88  return PCParser
89
90def loadBS(bsfile):
91  """Read in the 'BlockSchema' definitions of the attributes defining attributes"""
92  ii = open( bsfile, 'r' ).readlines()
93  ll = []
94  for l in ii:
95    ll.append( [x for x in l.strip().split('\t') ] )
96  cc = collections.defaultdict( dict )
97 
98  for l in ll[3:]:
99    l[-1] = l[-1] == '1'
100    if len(l) < len(ll[2]):
101      l.append( '' )
102    try:
103      for i in range( len(ll[2]) ):
104        cc[l[0]][ll[2][i]] = l[i]
105    except:
106      print (l)
107      raise
108  return cc
109
110class rechecks(object):
111  """Checks to be applied to strings"""
112  def __init__(self):
113    self.__isInt = re.compile( '-{0,1}[0-9]+' )
114
115  def isIntStr( self, tv ):
116    """Check whether a string is a valid representation of an integer."""
117    if type( tv ) not in [type(''),type(u'')]:
118      self.reason = 'NOT STRING'
119      return False
120    ok = self.__isInt.match( tv ) != None
121    if not ok:
122      self.reason = 'Failed to match regular expression for integers'
123    else:
124      self.reason = ''
125    return ok
126
127class dreqItemBase(object):
128       __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."""
129       _indexInitialised = False
130       _inx = None
131       _urlBase = ''
132       _htmlStyle = {}
133       _linkAttrStyle = {}
134
135       def __init__(self,idict=None,xmlMiniDom=None,id='defaultId',etree=False):
136         self._strictRead = True
137         dictMode = idict != None
138         mdMode = xmlMiniDom != None
139         self._htmlTtl = None
140         assert not( dictMode and mdMode), 'Mode must be either dictionary of minidom: both assigned'
141         assert dictMode or mdMode, 'Mode must be either dictionary of minidom: neither assigned'
142         ##self._defaults = { }
143         ##self._globalDefault = '__unset__'
144         self._contentInitialised = False
145         self._greenIcon = '<img height="12pt" src="/images/154g.png" alt="[i]"/>'
146         if dictMode:
147           self.dictInit( idict )
148         elif mdMode:
149           self.mdInit( xmlMiniDom, etree=etree )
150
151       def __repr__(self):
152         """Provide a one line summary of identifying the object."""
153         if self._contentInitialised:
154           return 'Item <%s>: [%s] %s' % (self._h.title,self.label,self.title)
155         else:
156           return 'Item <%s>: uninitialised' % self._h.title
157
158       def __info__(self,full=False):
159         """Print a summary of the data held in the object as a list of key/value pairs"""
160         if self._contentInitialised:
161           print ( 'Item <%s>: [%s] %s' % (self._h.title,self.label,self.title) )
162           for a in self.__dict__.keys():
163             if a[0] != '_' or full:
164               if hasattr( self._a[a], 'useClass') and self._a[a].useClass == 'internalLink' and self._base._indexInitialised:
165                 if self.__dict__[a] in self._base._inx.uid:
166                   targ = self._base._inx.uid[ self.__dict__[a] ]
167                   print ( '   %s: [%s]%s [%s]' % ( a, targ._h.label, targ.label, self.__dict__[a] ) )
168                 else:
169                   print ( '   %s: [ERROR: key not found] [%s]' % ( a, self.__dict__[a] ) )
170               else:
171                 print ( 'INFO:    %s: %s' % ( a, self.__dict__[a] ) )
172         else:
173           print ( 'Item <%s>: uninitialised' % self.sectionLabel )
174
175       def __href__(self,odir="",label=None,title=None):
176         """Generate html text for a link to this item."""
177         igns =  ['','__unset__']
178         if title == None:
179           if self._htmlTtl == None:
180             if 'description' in self.__dict__ and self.description != None and self.description.strip( ) not in igns:
181               ttl = self.description
182             elif 'title' in self.__dict__ and self.title != None and self.title.strip( ) not in igns:
183               ttl = self.title
184             else:
185               ttl = self.label
186             ttl = string.replace( ttl,'"', '&quot;' )
187             ttl = string.replace( ttl,'<', '&lt;' )
188             self._htmlTtl = string.replace( ttl,'>', '&gt;' )
189           title=self._htmlTtl
190         if label == None:
191             label = self.uid
192         if odir == '':
193           odir = './'
194             
195         return '<span title="%s"><a href="%s%s.html">%s</a></span>' % (title,odir,self.uid,label)
196
197       def htmlLinkAttrListStyle(self,a,targ,frm=None):
198           xx = string.join( ['%s [%s]' % (x.label, x.__href__()) for x in targ], '; ')
199           return '<li>%s: [%s] %s</li>' % ( a, targ[0]._h.label, xx )
200
201       def getHtmlLinkAttrStyle(self,a):
202           """Return a string containing a html fragment for a link to an attribute."""
203           if a in self.__class__._linkAttrStyle:
204             return self.__class__._linkAttrStyle[a]
205           else:
206             return lambda a,targ, frm='': '<li>%s: [%s] %s [%s]</li>' % ( a, targ._h.label, targ.label, targ.__href__() )
207
208       def __htmlLink__(self,a, sect,app):
209          """Create html line for a link or list of links"""
210          if self._a[a].useClass == 'internalLink':
211                   lst = self.getHtmlLinkAttrStyle(a)
212                   try:
213                     targ = self._base._inx.uid[ self.__dict__[a] ]
214                     m = lst( app, targ, frm=sect )
215                   except:
216                     print ( 'INFO.cex.00001:',a, self.__dict__[a], sect, self.label )
217                     m = '<li>%s: %s .... broken link</li>' % ( app, self.__dict__[a] )
218                     raise
219          else:
220            assert self._a[a].useClass == 'internalLinkList', 'Unrecognised useClass in __htmlLink__: %s' % self._a[a].useClass
221            m = self.htmlLinkAttrListStyle( app, [self._base._inx.uid[u] for u in self.__dict__[a] ], frm=sect )
222            return m
223          return m
224                   ##m = '<li>%s, %s: [%s] %s [%s]</li>' % ( a, self.__class__.__dict__[a].__href__(label=self._greenIcon), targ._h.label, targ.label, targ.__href__() )
225       def __html__(self,ghis=None):
226         """Create html view"""
227         msg = []
228         if self._contentInitialised:
229           sect = self._h.label
230           msg.append( '<h1>%s: [%s] %s</h1>' % (self._h.title,self.label,self.title) )
231           msg.append( '<a href="../index.html">Home</a> &rarr; <a href="../index/%s.html">%s section index</a><br/>\n' % (sect, self._h.title) )
232           msg.append( '<ul>' )
233           for a in self.__dict__.keys():
234             if a[0] != '_':
235               app = '%s%s' % (a, self.__class__.__dict__[a].__href__(label=self._greenIcon) )
236               if hasattr( self._a[a], 'useClass') and self._a[a].useClass in ['internalLink','internalLinkList'] and self._base._indexInitialised:
237                 if self.__dict__[a] == '__unset__':
238                   m = '<li>%s: %s [missing link]</li>' % ( app, self.__dict__[a] )
239                 else:
240                   m = self.__htmlLink__(a, sect,app)
241               elif hasattr( self._a[a], 'useClass') and self._a[a].useClass == 'externalUrl':
242                 m = '<li>%s: <a href="%s" title="%s">%s</a></li>' % ( app, self.__dict__[a], self._a[a].description, self._a[a].title )
243               else:
244                 m = '<li>%s: %s</li>' % ( app, self.__dict__[a] )
245               msg.append( m )
246           msg.append( '</ul>' )
247##
248## add list of inward references
249##
250           oldCode = False
251           if self._base._indexInitialised and oldCode:
252             f1 = self._htmlStyle.get( sect, {} ).get( 'getIrefs', None ) != None
253             if f1:
254               tl = []
255               if f1:
256                 tl = self._htmlStyle[sect]['getIrefs']
257               doall = '__all__' in tl
258               if doall:
259                 tl = self._inx.iref_by_sect[self.uid].a.keys()
260               tl1 = []
261               for t in tl:
262                 if t in self._inx.iref_by_sect[self.uid].a and len( self._inx.iref_by_sect[self.uid].a[t] ) > 0:
263                   tl1.append( t )
264               am = []
265               if len(tl1) > 0:
266                 am.append( '''<div class="demo">\n<div id="tabs">\n<ul>''' )
267                 for t in tl1:
268                   u0 = self._inx.iref_by_sect[self.uid].a[t][0]
269                   this1 = '<li><a href="#tabs-%s">%s</a></li>' % (t,self._inx.uid[u0]._h.title )
270                   am.append( this1 )
271                 am.append( '</ul>' )
272               for t in tl1:
273                   u0 = self._inx.iref_by_sect[self.uid].a[t][0]
274                   am.append( '<div id="tabs-%s">' % t )
275                   am.append( '<h3>%s</h3>' % self._inx.uid[u0]._h.title )
276                   am.append( '<ul>' )
277                   items = [self._inx.uid[u] for  u in self._inx.iref_by_sect[self.uid].a[t] ]
278                   items.sort( ds('label').cmp )
279                   for targ in items:
280                     if ghis == None:
281                       m = '<li>%s:%s [%s]</li>' % ( targ._h.label, targ.label, targ.__href__() )
282                     else:
283                       lst = ghis( targ._h.label )
284                       m = lst( targ, frm=sect )
285                     am.append( m )
286                   am.append( '</ul>' )
287                   am.append( '</div>' )
288               if len(am) > 0:
289                 am.append( '</div>' )
290                 msg.append( '<h2>Links from other sections</h2>' )
291                 msg.append( ''' <script>
292        $(function() {
293                $( "#tabs" ).tabs({cookie: { expires: 1 } });
294        });
295 </script>
296<!-- how to make tab selection stick: http://stackoverflow.com/questions/5066581/jquery-ui-tabs-wont-save-selected-tab-index-upon-page-reload  expiry time in days-->''' )
297                 for m in am:
298                    msg.append(m)
299               
300           elif self._base._indexInitialised:
301             msg += self.__irefHtml__(sect,ghis)
302         else:
303           msg.append( '<b>Item %s: uninitialised</b>' % self.sectionLabel )
304         return msg
305
306
307       def __irefHtml__(self, sect,ghis):
308         """Returns html (as a list of text strings) for inward references"""
309         if self._htmlStyle.get( sect, {} ).get( 'getIrefs', None ) == None:
310           return []
311         
312         tl = self._htmlStyle[sect]['getIrefs']
313         doall = '__all__' in tl
314         if doall:
315           tl = self._inx.iref_by_sect[self.uid].a.keys()
316         tl1 = []
317         for t in tl:
318           if t in self._inx.iref_by_sect[self.uid].a and len( self._inx.iref_by_sect[self.uid].a[t] ) > 0:
319             tl1.append( t )
320         am = []
321         if len(tl1) > 0:
322               am.append( '''<div class="demo">\n<div id="tabs">\n<ul>''' )
323               for t in tl1:
324                   u0 = self._inx.iref_by_sect[self.uid].a[t][0]
325                   this1 = '<li><a href="#tabs-%s">%s</a></li>' % (t,self._inx.uid[u0]._h.title )
326                   am.append( this1 )
327               am.append( '</ul>' )
328               for t in tl1:
329                   u0 = self._inx.iref_by_sect[self.uid].a[t][0]
330                   am.append( '<div id="tabs-%s">' % t )
331                   am.append( '<h3>%s</h3>' % self._inx.uid[u0]._h.title )
332                   am.append( '<ul>' )
333                   items = [self._inx.uid[u] for  u in self._inx.iref_by_sect[self.uid].a[t] ]
334                   items.sort( ds('label').cmp )
335                   for targ in items:
336                     if ghis == None:
337                       m = '<li>%s:%s [%s]</li>' % ( targ._h.label, targ.label, targ.__href__() )
338                     else:
339                       lst = ghis( targ._h.label )
340                       m = lst( targ, frm=sect )
341                     am.append( m )
342                   am.append( '</ul>' )
343                   am.append( '</div>' )
344               if len(am) > 0:
345                 am.append( '</div>' )
346                 am0 = [ '<h2>Links from other sections</h2>' ,]
347                 am0.append( ''' <script>
348        $(function() {
349                $( "#tabs" ).tabs({cookie: { expires: 1 } });
350        });
351 </script>
352<!-- how to make tab selection stick: http://stackoverflow.com/questions/5066581/jquery-ui-tabs-wont-save-selected-tab-index-upon-page-reload  expiry time in days-->''' )
353                 return am0 + am
354         else:
355           return []
356               
357       def dictInit( self, idict ):
358         __doc__ = """Initialise from a dictionary."""
359         for a in self._a.keys():
360           vv = False
361           if a in idict:
362             val = idict[a]
363             vv = True
364           else:
365             if type( self.__class__.__dict__[a] ) not in (type( ''), type( u'' )) and (not self.__class__.__dict__[a].required):
366               if hasattr( self, a):
367                 setattr( self, a, None )
368                 ##self.a = None
369                 delattr( self, a )
370                 ##if a in self.__dict__:
371                   ##self.__dict__.pop(a)
372               else:
373                 print ( 'ERROR: attempt to remove non-existent attribute: %s' % a )
374             else:
375               val = self._d.defaults.get( a, self._d.glob )
376               vv = True
377           if vv:
378             setattr( self, a, val )
379         self._contentInitialised = True
380
381       def mdInit( self, el, etree=False ):
382         __doc__ = """Initialisation from a mindom XML element. The list of attributes must be set by the class factory before the class is initialised"""
383         deferredHandling=False
384         nw1 = 0
385         tvtl = []
386         if etree:
387           ks = set( el.keys() )
388           for a in self._a.keys():
389             if a in ks:
390               aa = '%s%s' % (self.ns,a)
391               tvtl.append( (a,True, str( el.get( a ) ) ) )
392             elif self._a[a].__dict__.get( 'required', True ) in [False,'false',u'false']:
393               tvtl.append( (a,True,None) )
394             else:
395               tvtl.append( (a,False,None) )
396         else:
397           for a in self._a.keys():
398             if el.hasAttribute( a ):
399               tvtl.append( (a,True, str( el.getAttribute( a ) ) ) )
400##
401## attributes are treated as required unless they have a required attribute set to false
402##
403             elif self._a[a].__dict__.get( 'required', True ) not in [False,'false',u'false']:
404               tvtl.append( (a,False,None) )
405       
406         for a,tv,v in tvtl:
407           if tv:
408             erase = False
409             if v == None:
410               pass
411               erase = True
412             elif self._a[a].type == u'xs:float':
413               if v == '':
414                 v = None
415               else:
416                 try:
417                   v = float(v)
418                 except:
419                   print ( 'Failed to convert real number: %s,%s,%s' % (a,tv,v) )
420                   raise
421             elif self._a[a].type in [u'aa:st__floatList', u'aa:st__floatListMonInc']:
422                 v = [float(x) for x in v.split()]
423             elif self._a[a].type in [u'aa:st__integerList', u'aa:st__integerListMonInc']:
424                 v = [int(x) for x in v.split()]
425                 if self._a[a].type in [u'aa:st__integerListMonInc'] and self._strictRead:
426                   for i in range(len(v)-1):
427                     assert v[i] < v[i+1], 'Attribute %s of type %s with non-monotonic value: %s' % (a,self._a[a].type,str(v))
428             elif self._a[a].type == u'xs:integer':
429               if self._rc.isIntStr( v ):
430                 v = int(v)
431               else:
432                 v = v.strip()
433                 thissect = '%s [%s]' % (self._h.title,self._h.label)
434                 if v in [ '',u'',' ', u' ', [], '[]']:
435                   if nw1 < 20:
436                     print ( 'WARN.050.0001: input integer non-compliant: %s: %s: "%s" -- set to zero' % (thissect,a,v) )
437                     nw1 += 1
438                   v = 0
439                 else:
440                   try:
441                     v = int(float(v))
442                     print ( 'WARN: input integer non-compliant: %s: %s: %s' % (thissect,a,v) )
443                   except:
444                     msg = 'ERROR: failed to convert integer: %s: %s: %s, %s' % (thissect,a,v,type(v))
445                     deferredHandling=True
446             elif self._a[a].type == u'xs:boolean':
447               v = v in ['true','1']
448             elif self._a[a].type == u'aa:st__stringList':
449               if v.find(' ') != -1:
450                 v = v.split()
451               else:
452                 v = [v,]
453             elif self._a[a].type not in [u'xs:string']:
454               print ('ERROR: Type %s not recognised [%s:%s]' % (self._a[a].type,self._h.label,a) )
455
456             if erase:
457               ### need to overwrite attribute (which is inherited from parent class) before deleting it.
458               ### this may not be needed in python3
459               self.__dict__[a] = '__tmp__'
460               delattr( self, a )
461             else:
462               self.__dict__[a] = v
463           else:
464             if a in ['uid',]:
465               thissect = '%s [%s]' % (self._h.title,self._h.tag)
466               print ( 'ERROR.020.0001: missing uid: %s' % thissect )
467               if etree:
468                 print ( ks )
469                 import sys
470                 sys.exit(0)
471             self.__dict__[a] = self._d.defaults.get( a, self._d.glob )
472
473           if deferredHandling:
474             print ( msg )
475
476         self._contentInitialised = True
477
478   
479class config(object):
480  """Read in a vocabulary collection configuration document and a vocabulary document"""
481
482  def __init__(self, configdoc='out/dreqDefn.xml', thisdoc='../workbook/trial_20150724.xml', manifest=None, useShelve=False, strings=False):
483    self.rc = rechecks()
484    self.lu = lutilsC()
485    self.silent = True
486    self.coll = {}
487
488    self.nts = collections.namedtuple( 'sectdef', ['tag','label','title','id','itemLabelMode','level','maxOccurs','labUnique','uid'] )
489    self.nti = collections.namedtuple( 'itemdef', ['tag','label','title','type','useClass','techNote','required'] )
490    self.ntt = collections.namedtuple( 'sectinit', ['header','attributes','defaults'] )
491    self.nt__default = collections.namedtuple( 'deflt', ['defaults','glob'] )
492    self.ntf = collections.namedtuple( 'sect', ['header','attDefn','items'] )
493    self.bscc = loadBS(blockSchemaFile)
494    self.strings = strings
495
496    self.tt0 = {}
497    self.tt1 = {}
498    self.ttl2 = []
499    self.docs = {}
500
501    if manifest != None:
502      assert os.path.isfile( manifest ), 'Manifest file not found: %s' % manifest
503      ii = open(manifest).readlines() 
504      docl = []
505      for l in ii[1:]:
506        bits = l.strip().split()
507        assert len( bits ) > 1, 'Failed to parse line in manifest %s: \n%s' % (manifest,l)
508        for b in bits[:2]:
509          assert os.path.isfile( b ), 'File %s not found (listed in %s)' % (b,manifest )
510        docl.append( tuple( bits[:2] ) )
511      for d,c in docl:
512        self.__read__(d, c)
513    else:
514      self.__read__(thisdoc, configdoc)
515
516  def getByUid(self,uid):
517    mmm = []
518    for fn,rr in self.docs.items():
519       root, doc = rr
520       mm = root.findall( ".//*[@uid='%s']" % uid )
521       mmm += mm
522       print ('%s: %s' % (fn, str(mm)) )
523    return mmm
524
525  def __read__(self, thisdoc, configdoc):
526    self.vdef = configdoc
527    self.vsamp = thisdoc
528    fn = thisdoc.split( '/' )[-1]
529
530    if self.strings:
531      doc = xml.dom.minidom.parseString( self.vdef  )
532    else:
533      doc = xml.dom.minidom.parse( self.vdef  )
534##
535## elementTree parsing implemented for main document
536##
537    self.etree = False
538    self.etree = True
539    if self.etree:
540      import xml.etree.cElementTree as cel
541     
542      cel.register_namespace('', "urn:w3id.org:cmip6.dreq.dreq:a")
543      cel.register_namespace('pav', "http://purl.org/pav/2.3")
544
545      if not self.strings:
546        if python2:
547          parser = getParser()()
548          self.contentDoc = cel.parse( self.vsamp, parser=parser )
549        else:
550          self.contentDoc = cel.parse( self.vsamp )
551
552        root = self.contentDoc.getroot()
553      else:
554        root = cel.fromstring(self.vsamp)
555      ##bs = string.split( root.tag, '}' )
556      self.docs[fn] = (root, self.contentDoc)
557      bs = root.tag.split( '}' )
558      if len( bs ) > 1:
559        self.ns = bs[0] + '}'
560      else:
561        self.ns = None
562    else:
563      if self.strings:
564        self.contentDoc = xml.dom.minidom.parseString( self.vsamp  )
565      else:
566        self.contentDoc = xml.dom.minidom.parse( self.vsamp )
567      self.ns = None
568
569    vl = doc.getElementsByTagName( 'table' ) + doc.getElementsByTagName( 'annextable' )
570    self.tables = {}
571    tables = {}
572    self.tableClasses = {}
573    self.tableItems = collections.defaultdict( list )
574##
575## this loads in some metadata, but not yet in a useful way.
576##
577    self._t0 = self.parsevcfg(None)
578    self._t2 = self.parsevcfg('__main__')
579    self._tableClass0 = self.lu.itemClassFact( self._t0, ns=self.ns )
580##
581## define a class for the section heading records.
582##
583    self._t1 = self.parsevcfg('__sect__')
584##
585## when used with manifest .. need to preserve entries in "__main__" from each document.
586##
587    self._sectClass0 = self.lu.itemClassFact( self._t1, ns=self.ns )
588
589    for k in self.bscc:
590      self.tt0[k] = self._tableClass0(idict=self.bscc[k])
591      if k in self._t0.attributes:
592        setattr( self._tableClass0, '%s' % k, self.tt0[k] )
593      if k in self._t1.attributes:
594        setattr( self._sectClass0, '%s' % k, self.tt0[k] )
595
596##
597## save header information, as for recordAttributeDefn below
598##
599    self._recAtDef = {'__core__':self._t0, '__sect__':self._t1}
600##
601## addition of __core__ to coll dictionary ..
602##
603    self.coll['__core__'] = self.ntf( self._t0.header, self._t0.attributes, [self.tt0[k] for k in self.tt0] )
604
605    ec = {}
606    for i in self.coll['__core__'].items:
607      ec[i.label] = i
608
609    for v in vl:
610      t = self.parsevcfg(v)
611      tables[t[0].label] = t
612      self.tableClasses[t[0].label] = self.lu.itemClassFact( t, ns=self.ns )
613      thisc = self.tableClasses[t[0].label]
614      self.tt1[t[0].label] = self._sectClass0( idict=t.header._asdict() )
615      self.tt1[t[0].label].maxOccurs = t.header.maxOccurs
616      self.tt1[t[0].label].labUnique = t.header.labUnique
617      self.tt1[t[0].label].level = t.header.level
618      self.tt1[t[0].label].itemLabelMode = t.header.itemLabelMode
619      self.ttl2 += [thisc.__dict__[a] for a in t.attributes]
620    mil = [t[1] for t in self._t2.attributes.items()]
621    self.coll['__main__'] = self.ntf( self._t2.header, self._t2.attributes, self.ttl2 )
622
623    self.coll['__sect__'] = self.ntf( self._t1.header, self._t1.attributes, [self.tt1[k] for k in self.tt1] )
624
625    for sct in ['__core__','__main__','__sect__']:
626      for k in self.coll[sct].attDefn.keys():
627        assert k in ec, 'Key %s [%s] not found' % (k,sct)
628        self.coll[sct].attDefn[k] = ec[k]
629
630    self.recordAttributeDefn = tables
631    for k in tables.keys():
632      if self.etree:
633        vl = root.findall( './/%s%s' % (self.ns,k) )
634        if len(vl) == 1:
635          v = vl[0]
636          t = v.get( 'title' )
637          i = v.get( 'id' )
638          uid = v.get( 'uid' )
639          useclass = v.get( 'useClass' )
640
641          self.tt1[k].label = k
642          self.tt1[k].title = t
643          self.tt1[k].id = i
644          self.tt1[k].uid = uid
645          self.tt1[k].useClass = useclass
646          self.tableClasses[k]._h = self.tt1[k]
647          il = v.findall( '%sitem' % self.ns )
648          self.info( '%s, %s, %s, %s' % ( k, t, i, len(il) ) )
649 
650          self.tables[k] = (i,t,len(il))
651       
652          for i in il:
653            ii = self.tableClasses[k](xmlMiniDom=i, etree=True)
654            self.tableItems[k].append( ii )
655        elif len(vl) > 1:
656          assert False, 'not able to handle repeat sections with etree yet'
657      else:
658        vl = self.contentDoc.getElementsByTagName( k )
659        if len(vl) == 1:
660          v = vl[0]
661          t = v.getAttribute( 'title' )
662          i = v.getAttribute( 'id' )
663          il = v.getElementsByTagName( 'item' )
664          self.info( '%s, %s, %s, %s' % ( k, t, i, len(il) ) )
665 
666          self.tables[k] = (i,t,len(il))
667       
668          for i in il:
669            ii = self.tableClasses[k](xmlMiniDom=i)
670            self.tableItems[k].append( ii )
671        elif len(vl) > 1:
672          l1 = []
673          l2 = []
674          for v in vl:
675            t = v.getAttribute( 'title' )
676            i = v.getAttribute( 'id' )
677            il = v.getElementsByTagName( 'item' )
678            self.info( '%s, %s, %s, %s' % ( k, t, i, len(il) ) )
679            l1.append( (i,t,len(il)) )
680         
681            l2i = []
682            for i in il:
683              ii = self.tableClasses[k](xmlMiniDom=i)
684              l2i.append( ii )
685            l2.append( l2i )
686          self.tables[k] = l1
687          self.tableItems[k] = l2
688      self.coll[k] = self.ntf( self.recordAttributeDefn[k].header, self.recordAttributeDefn[k].attributes, self.tableItems[k] )
689 
690  def info(self,ss):
691    """Switchable print function ... switch off by setting self.silent=True"""
692    if not self.silent:
693      print ( ss )
694
695  def parsevcfg(self,v):
696      """Parse a section definition element, including all the record attributes. The results are returned as a named tuple of attributes for the section and a dictionary of record attribute specifications."""
697      if v in [ None,'__main__']:
698        idict = {'description':'An extended description of the object', 'title':'Record Description', \
699           'techNote':'', 'useClass':'__core__', 'superclass':'rdf:property',\
700           'type':'xs:string', 'uid':'__core__:description', 'label':'label', 'required':'required' }
701        if v == None:
702          vtt = self.nts( '__core__', 'CoreAttributes', 'X.1 Core Attributes', '00000000', 'def', '0', '0', 'false', '__core__' )
703        else:
704          vtt = self.nts( '__main__', 'DataRequestAttributes', 'X.2 Data Request Attributes', '00000001', 'def', '0', '0', 'false', '__main__' )
705      elif v == '__sect__':
706        idict = {'title':'Record Description', \
707         'uid':'__core__:description', 'label':'label', 'useClass':'text', 'id':'id', 'maxOccurs':'', 'itemLabelMode':'', 'level':'', 'labUnique':'' }
708        vtt = self.nts( '__sect__', 'sectionAttributes', 'X.3 Section Attributes', '00000000', 'def', '0', '0', 'false', '__sect__' )
709##<var label="var" uid="SECTION:var" useClass="vocab" title="MIP Variable" id="cmip.drv.001">
710      else:
711        l = v.getAttribute( 'label' )
712        t = v.getAttribute( 'title' )
713        i = v.getAttribute( 'id' )
714        ilm = v.getAttribute( 'itemLabelMode' )
715        lev = v.getAttribute( 'level' )
716        maxo = v.getAttribute( 'maxOccurs' )
717        labu = v.getAttribute( 'labUnique' )
718        il = v.getElementsByTagName( 'rowAttribute' )
719        vtt = self.nts( v.nodeName, l,t,i,ilm,lev, maxo, labu, 's__%s' % v.nodeName )
720        idict = {}
721        for i in il:
722          tt = self.parseicfg(i)
723          idict[tt.label] = tt
724      deflt = self.nt__default( {}, '__unset__' )
725
726      ## return a named tuple: (header, attributes, defaults)
727      return self.ntt( vtt, idict, deflt )
728
729  def parseicfg(self,i):
730      """Parse a record attribute specification"""
731      defs = {'type':"xs:string"}
732      ll = []
733      ee = {}
734      for k in ['label','title','type','useClass','techNote','description','uid','required']:
735        if i.hasAttribute( k ):
736          ll.append( i.getAttribute( k ) )
737        else:
738          ll.append( defs.get( k, None ) )
739        ee[k] = ll[-1]
740      l, t, ty, cls, tn, desc, uid, rq = ll
741      self.lastTitle = t
742      if rq in ['0', 'false']:
743        rq = False
744      else:
745        rq = True
746      ee['required'] = rq
747
748      returnClass = True
749      if returnClass:
750        return self._tableClass0( idict=ee )
751      else:
752        return self.nti( i.nodeName, l,t,ty,cls,tn,rq )
753
754class container(object):
755  """Simple container class, to hold a set of dictionaries of lists."""
756  def __init__(self, atl ):
757    self.uid = {}
758    for a in atl:
759      self.__dict__[a] =  collections.defaultdict( list )
760
761class c1(object):
762  def __init__(self):
763    self.a = collections.defaultdict( list )
764
765class index(object):
766  """Create an index of the document. Cross-references are generated from attributes with class 'internalLink'.
767This version assumes that each record is identified by an "uid" attribute and that there is a "var" section.
768Invalid internal links are recorded in tme "missingIds" dictionary.
769For any record, with identifier u, iref_by_uid[u] gives a list of the section and identifier of records linking to that record.
770"""
771
772  def __init__(self, dreq,lock=True):
773    self.silent = True
774    self.uid = {}
775    self.uid2 = collections.defaultdict( list )
776    nativeAtts = ['uid','iref_by_uid','iref_by_sect','missingIds']
777    naok = map( lambda x: not x in dreq, nativeAtts )
778    assert all(naok), 'This version cannot index collections containing sections with names: %s' % str( nativeAtts )
779    self.var_uid = {}
780    self.var_by_name = collections.defaultdict( list )
781    self.var_by_sn = collections.defaultdict( list )
782    self.iref_by_uid = collections.defaultdict( list )
783    irefdict = collections.defaultdict( list )
784    for k in dreq.keys():
785      if 'sn' in dreq[k].attDefn:
786         self.__dict__[k] =  container( ['label','sn'] )
787      else:
788         self.__dict__[k] =  container( ['label'] )
789    ##
790    ## collected names of attributes which carry internal links
791    ##
792      for ka in dreq[k].attDefn.keys():
793        if hasattr( dreq[k].attDefn[ka], 'useClass') and dreq[k].attDefn[ka].useClass in  ['internalLink','internalLinkList']:
794           irefdict[k].append( ka )
795
796    for k in dreq.keys():
797        for i in dreq[k].items:
798          assert 'uid' in i.__dict__, 'uid not found::\n%s\n%s' % (str(i._h),str(i.__dict__) )
799          if 'uid' in self.uid:
800            print ( 'ERROR.100.0001: Duplicate uid: %s [%s]' % (i.uid,i._h.title) )
801            self.uid2[i.uid].append( (k,i) )
802          else:
803### create index bx uid.
804            self.uid[i.uid] = i
805
806    self.missingIds = collections.defaultdict( list )
807    self.iref_by_sect = collections.defaultdict( c1 )
808    for k in dreq.keys():
809        for k2 in irefdict.get( k, [] ):
810          n1 = 0
811          n2 = 0
812          for i in dreq[k].items:
813            if k2 in i.__dict__:
814              id2 = i.__dict__.get( k2 )
815              if id2 != '__unset__':
816                sect = i._h.label
817  ## append attribute name and target  -- item i.uid, attribute k2 reference item id2
818                if type(id2) != type( [] ):
819                  id2 = [id2,]
820                for u in id2:
821                  self.iref_by_uid[ u ].append( (k2,i.uid) )
822                  self.iref_by_sect[ u ].a[sect].append( i.uid )
823                  if u in self.uid:
824                    n1 += 1
825                  else:
826                    n2 += 1
827                    self.missingIds[u].append( (k,k2,i.uid) )
828          self.info(  'INFO:: %s, %s%s (%s)' % (k,k2,n1,n2) )
829
830    for k in dreq.keys():
831      for i in dreq[k].items:
832        self.__dict__[k].uid[i.uid] = i
833        self.__dict__[k].label[i.label].append( i.uid )
834        if 'sn' in dreq[k].attDefn:
835          self.__dict__[k].sn[i.sn].append( i.uid )
836
837    if lock:
838      for k in self.iref_by_uid: 
839         self.iref_by_uid[k] = tuple( self.iref_by_uid[k] )
840      for k in self.iref_by_sect:
841        for s in self.iref_by_sect[ k ].a:
842          self.iref_by_sect[ k ].a[s] = tuple( self.iref_by_sect[ k ].a[s] )
843
844  def info(self,ss):
845    if not self.silent:
846      print ( ss )
847
848class ds(object):
849  """Comparison object to assist sorting of lists of dictionaries"""
850  def __init__(self,k):
851    self.k = k
852  def cmp(self,x,y):
853    return cmp( x.__dict__[self.k], y.__dict__[self.k] )
854
855class kscl(object):
856  """Comparison object to assist sorting of dictionaries of class instances"""
857  def __init__(self,idict,k):
858    self.k = k
859    self.idict = idict
860  def cmp(self,x,y):
861    return cmp( self.idict[x].__dict__[self.k], self.idict[y].__dict__[self.k] )
862
863src1 = '../workbook/trial_20150831.xml'
864
865#DEFAULT LOCATION -- changed automatically when building distribution
866defaultDreq = 'dreq.xml'
867#DEFAULT CONFIG
868defaultConfig = 'dreq2Defn.xml'
869
870defaultDreqPath = '%s/%s' % (DOC_DIR, defaultDreq )
871defaultConfigPath = '%s/%s' % (DOC_DIR, defaultConfig )
872
873class loadDreq(object):
874  """Load in a vocabulary document.
875  dreqXML: full path to the XML document
876  configdoc: full path to associated configuration document
877  useShelve: flag to specify whether to retrieve data from cache (not implemented)
878  htmlStyles: dictionary of styling directives which influence structure of html page generates by the "makeHtml" method
879"""
880
881  def __init__(self,dreqXML=defaultDreqPath, configdoc=defaultConfigPath, useShelve=False, htmlStyles=None, strings=False, manifest=None ):
882    self.c = config( thisdoc=dreqXML, configdoc=configdoc, useShelve=useShelve,strings=strings,manifest=manifest)
883    self.coll = self.c.coll
884    self.inx = index(self.coll)
885    self.itemStyles = {}
886    self.defaultItemLineStyle = lambda i, frm='', ann='': '<li>%s: %s</li>' % ( i.label, i.__href__(odir='../u/') )
887    self.version = version
888##
889## add index to Item base class .. so that it can be accessed by item instances
890##
891    dreqItemBase._inx = self.inx
892    dreqItemBase._indexInitialised = True
893##
894## load in additional styling directives
895##
896    if htmlStyles != None:
897      for k in htmlStyles:
898        dreqItemBase._htmlStyle[k] = htmlStyles[k]
899
900##    dreqItemBase._htmlStyle['__general__'] = {'addRemarks':True}
901
902    self.pageTmpl = """<html><head><title>%s</title>
903%s
904<link rel="stylesheet" type="text/css" href="%scss/dreq.css">
905</head><body>
906
907<div id="top">
908   <div id="corner"></div>
909   CMIP6 Data Request
910</div>
911<div id="nav"><div><a href="%s" title="Home">Home</a></div></div>
912
913<div id="section">
914%s
915</div>
916</body></html>"""
917
918  def getHtmlItemStyle(self, sect):
919    """Get the styling method associated with a given section."""
920    if sect in self.itemStyles:
921      return self.itemStyles[sect]
922    return self.defaultItemLineStyle
923
924  def updateByUid( self, uid, dd, delete=[] ):
925    typePar={'xs:string':'x', 'xs:integer':0, 'xs:float':1., 'xs:boolean':True, 'xs:duration':'x'}
926    listTypePar={ "aa:st__integerList":1,"aa:st__integerListMonInc":1, "aa:st__stringList":'x', "aa:st__floatList":1. }
927
928    mmm = self.c.getByUid( uid )
929    assert len(mmm) == 1, 'Expected single uid match, found: %s' % len(mmm)
930    thisdoc = mmm[0]
931    item = self.inx.uid[uid]
932    d1 = [d for d in delete if d not in item._a]
933    e1 = len(d1) > 0
934    if e1:
935      print ('ERROR.update.0001: request to delete non-existent keys: %s' % d1 )
936    d1 = [d for d in delete if d in item._a and item._a[d].required ]
937    e1b = len(d1) > 0
938    if e1b:
939      print ('ERROR.update.0002: request to delete required attributes: %s' % d1 )
940    d2 = [d for d in dd if d not in item._a]
941    e2 = len(d2) > 0
942    if e2:
943      print ('ERROR.update.0003: request to modify non-existent keys: %s' % d2 )
944    e3 = []
945    for k in [d for d in dd if d in item._a]:
946      if item._a[k].type in typePar:
947        e3.append( type( dd[k] ) != type( typePar[item._a[k].type] ) )
948        if e3[-1]:
949          print ('ERROR.update.0004: value has wrong type [%s] %s --- %s' % (item._a[k].type, dd[k], type( dd[k] ) ) )
950      elif item._a[k].type in listTypePar:
951        a = type( dd[k] ) not in [type( [] ), type( () )]
952        if a:
953          print ('ERROR.update.0005: value has wrong type [%s] %s --- should be list or tuple' % (item._a[k].type, dd[k] ) )
954        else:
955          a = not all( [type(x) == type(listTypePar[item._a[k].type]) for x in dd[k]] )
956          if a:
957            print ('ERROR.update.0005: value has wrong type [%s] %s --- %s' % (item._a[k].type, dd[k], [type(x)for x in dd[k]] ) )
958
959        if not a and item._a[k].type == 'aa:st__integerListMonInc':
960          a = not all( [dd[k][i+1] > dd[k][i] for i in range( len(dd[k]) -1 )] )
961          if a:
962            print ('ERROR.update.0006: value should be a monotonic increasing integer list: %s' % str( dd[k] ) )
963        e3.append(a)
964      else:
965        print ( 'Type not recognised: %s' % item._a[k].type )
966        e3.append( False )
967    eee = any([e1,e1b,e2,any(e3)])
968    assert not eee, 'STOPPING: validation errors'
969    self.thisdoc = thisdoc
970    for k in dd:
971      thisdoc.set( k, self.__string4xml__( dd[k], item._a[k].type ) )
972      item.__dict__[k] = dd[k]
973
974  def saveXml( self, docfn=None, targ=None ):
975    if docfn == None:
976      docfn, tt = self.c.docs.items()[0]
977    else:
978      tt = self.c.docs[docfn]
979
980    if targ == None:
981      targ = docfn
982    tt[1].write( targ )
983    print ('XML document saved to %s' % targ)
984 
985  def __string4xml__(self,v,typ):
986    if typ in ['xs:string','xs:duration']:
987       return v
988    elif typ in ['xs:integer', 'xs:float', 'xs:boolean']:
989       return str(v)
990    elif typ == "aa:st__stringList":
991       return string.join(v)
992    elif typ in ["aa:st__integerList","aa:st__integerListMonInc", "aa:st__floatList"]:
993       return string.join( [str(x) for x in v] )
994    else:
995       assert False, 'Data type not recognised'
996     
997  def _sectionSortHelper(self,title):
998
999    ab = title.split(  )[0].split('.')
1000    if len( ab ) == 2:
1001      a,b = ab
1002
1003      if self.c.rc.isIntStr(a):
1004        a = int(a)
1005      if self.c.rc.isIntStr(b):
1006        b = int(b)
1007      rv = (a,b)
1008    elif len(ab) == 1:
1009      rv = (ab[0],0)
1010    else:
1011      rv = ab
1012    return rv
1013
1014  def makeHtml(self,odir='./html', ttl0 = 'Data Request Index', annotations=None):
1015    """Generate a html view of the vocabularies, using the "__html__" method of the vocabulary item class to generate a
1016page for each item and also generating index pages.
1017    odir: directory for html files;
1018    ttl0: Title for main index (in odir/index.html)"""
1019
1020    ks = self.inx.uid.keys()
1021    ks.sort( kscl( self.inx.uid, 'title' ).cmp )
1022    for k in ks:
1023      i = self.inx.uid[k]
1024      ttl = 'Data Request Record: [%s]%s' % (i._h.label,i.label)
1025      bdy = string.join( i.__html__( ghis=self.getHtmlItemStyle ), '\n' )
1026      oo = open( '%s/u/%s.html' % (odir,i.uid), 'w' )
1027      oo.write( self.pageTmpl % (ttl, jsh, '../', '../index.html', bdy ) )
1028      oo.close()
1029
1030    msg0 = ['<h1>%s</h1>' % ttl0, '<ul>',]
1031    msg0.append( '<li><a href="tab01_1_1.html">Overview: priority 1 variables, tier 1 experiments</a></li>' )
1032    msg0.append( '<li><a href="tab01_3_3.html">Overview: all variables and experiments</a></li>' )
1033    ks = sorted( self.coll.keys() )
1034    ee = {}
1035    for k in ks:
1036      ee[self.coll[k].header.title] = k
1037    kks = sorted( ee.keys(),  key = self._sectionSortHelper )
1038    for kt in kks:
1039      k = ee[kt]
1040##
1041## sort on item label
1042##
1043      if annotations != None and k in annotations:
1044        ann = annotations[k]
1045      else:
1046        ann = {}
1047
1048      self.coll[k].items.sort( ds('label').cmp )
1049      ttl = 'Data Request Section: %s' % k
1050      msg0.append( '<li><a href="index/%s.html">%s [%s]</a></li>\n' % (k,self.coll[k].header.title,k) )
1051      msg = ['<h1>%s</h1>\n' % ttl, '<ul>',]
1052      msg.append( '<a href="../index.html">Home</a><br/>\n' )
1053      lst = self.getHtmlItemStyle(k)
1054     
1055      for i in self.coll[k].items:
1056        ##m = '<li>%s: %s</li>' % ( i.label, i.__href__(odir='../u/') )
1057       
1058        m = lst( i, ann=ann.get( i.label ) )
1059        msg.append( m )
1060      msg.append( '</ul>' )
1061      bdy = string.join( msg, '\n' )
1062      oo = open( '%s/index/%s.html' % (odir,k), 'w' )
1063      oo.write( self.pageTmpl % (ttl, '', '../', '../index.html', bdy ) )
1064      oo.close()
1065    msg0.append( '</ul>' )
1066    bdy = string.join( msg0, '\n' )
1067    oo = open( '%s/index.html' % odir, 'w' )
1068    oo.write( self.pageTmpl % (ttl0, '', '', 'index.html', bdy ) )
1069    oo.close()
1070   
1071if __name__ == '__main__':
1072  dreq = loadDreq(manifest='out/dreqManifest.txt' )
1073
Note: See TracBrowser for help on using the repository browser.