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

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

candiate 01.beta.33

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