source: CMIP6dreqbuild/trunk/src/framework/dreqPy/dreq.py @ 782

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

01.beta.38

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