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

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

Updated setup for tag 01.beta.32

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