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

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

corrections to default directories with manifest

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
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          if not os.path.isfile( b ):
533             b = '%s/%s' % (PACKAGE_DIR,b)
534          assert os.path.isfile( b ), 'File %s not found (listed in %s)' % (b,manifest )
535        docl.append( tuple( bits[:2] ) )
536      for d,c in docl:
537        self.__read__(d, c)
538    else:
539      self.__read__(thisdoc, configdoc)
540
541  def getByUid(self,uid):
542    mmm = []
543    for fn,rr in self.docs.items():
544       root, doc = rr
545       mm = root.findall( ".//*[@uid='%s']" % uid )
546       mmm += mm
547       print ('%s: %s' % (fn, str(mm)) )
548    return mmm
549
550  def __read__(self, thisdoc, configdoc):
551    self.vdef = configdoc
552    self.vsamp = thisdoc
553    fn = thisdoc.split( '/' )[-1]
554
555    if self.strings:
556      doc = xml.dom.minidom.parseString( self.vdef  )
557    else:
558      doc = xml.dom.minidom.parse( self.vdef  )
559##
560## elementTree parsing implemented for main document
561##
562    self.etree = False
563    self.etree = True
564    if self.etree:
565      import xml.etree.cElementTree as cel
566     
567      if not pythonPre27:
568        ## this for of namespace registration not available in 2.6
569        ## absence of registration means module cannot write data exactly as read.
570        ##
571        cel.register_namespace('', "urn:w3id.org:cmip6.dreq.dreq:a")
572        cel.register_namespace('pav', "http://purl.org/pav/2.3")
573
574      if not self.strings:
575        if python2:
576          parser = getParser()()
577          self.contentDoc = cel.parse( self.vsamp, parser=parser )
578        else:
579          self.contentDoc = cel.parse( self.vsamp )
580
581        root = self.contentDoc.getroot()
582      else:
583        root = cel.fromstring(self.vsamp)
584      ##bs = string.split( root.tag, '}' )
585      self.docs[fn] = (root, self.contentDoc)
586      bs = root.tag.split( '}' )
587      if len( bs ) > 1:
588        self.ns = bs[0] + '}'
589      else:
590        self.ns = None
591      vl = root.findall( './/{http://purl.org/pav/2.3}version' )
592      self.version = vl[0].text
593    else:
594      if self.strings:
595        self.contentDoc = xml.dom.minidom.parseString( self.vsamp  )
596      else:
597        self.contentDoc = xml.dom.minidom.parse( self.vsamp )
598
599        vl = self.contentDoc.getElementsByTagName( 'prologue' )
600        v = vl[0].getElementsByTagName( 'pav:version' )
601        self.version = v[0].firstChild.data
602      self.ns = None
603
604    vl = doc.getElementsByTagName( 'table' ) + doc.getElementsByTagName( 'annextable' )
605    self.tables = {}
606    tables = {}
607    self.tableClasses = {}
608    self.tableItems = collections.defaultdict( list )
609##
610## this loads in some metadata, but not yet in a useful way.
611##
612    self._t0 = self.parsevcfg(None)
613    self._t2 = self.parsevcfg('__main__')
614    self._tableClass0 = self.lu.itemClassFact( self._t0, ns=self.ns )
615##
616## define a class for the section heading records.
617##
618    self._t1 = self.parsevcfg('__sect__')
619##
620## when used with manifest .. need to preserve entries in "__main__" from each document.
621##
622    self._sectClass0 = self.lu.itemClassFact( self._t1, ns=self.ns )
623
624    for k in self.bscc:
625      self.tt0[k] = self._tableClass0(idict=self.bscc[k])
626      if k in self._t0.attributes:
627        setattr( self._tableClass0, '%s' % k, self.tt0[k] )
628      if k in self._t1.attributes:
629        setattr( self._sectClass0, '%s' % k, self.tt0[k] )
630
631##
632## save header information, as for recordAttributeDefn below
633##
634    self._recAtDef = {'__core__':self._t0, '__sect__':self._t1}
635##
636## addition of __core__ to coll dictionary ..
637##
638    self.coll['__core__'] = self.ntf( self._t0.header, self._t0.attributes, [self.tt0[k] for k in self.tt0] )
639
640    ec = {}
641    for i in self.coll['__core__'].items:
642      ec[i.label] = i
643
644    for v in vl:
645      t = self.parsevcfg(v)
646      tables[t[0].label] = t
647      self.tableClasses[t[0].label] = self.lu.itemClassFact( t, ns=self.ns )
648      thisc = self.tableClasses[t[0].label]
649      self.tt1[t[0].label] = self._sectClass0( idict=t.header._asdict() )
650      self.tt1[t[0].label].maxOccurs = t.header.maxOccurs
651      self.tt1[t[0].label].labUnique = t.header.labUnique
652      self.tt1[t[0].label].level = t.header.level
653      self.tt1[t[0].label].itemLabelMode = t.header.itemLabelMode
654      self.ttl2 += [thisc.__dict__[a] for a in t.attributes]
655    mil = [t[1] for t in self._t2.attributes.items()]
656    self.coll['__main__'] = self.ntf( self._t2.header, self._t2.attributes, self.ttl2 )
657
658    self.coll['__sect__'] = self.ntf( self._t1.header, self._t1.attributes, [self.tt1[k] for k in self.tt1] )
659
660    for sct in ['__core__','__main__','__sect__']:
661      for k in self.coll[sct].attDefn.keys():
662        assert k in ec, 'Key %s [%s] not found' % (k,sct)
663        self.coll[sct].attDefn[k] = ec[k]
664
665    self.recordAttributeDefn = tables
666    for k in tables.keys():
667      if self.etree:
668        vl = root.findall( './/%s%s' % (self.ns,k) )
669        if len(vl) == 1:
670          v = vl[0]
671          t = v.get( 'title' )
672          i = v.get( 'id' )
673          uid = v.get( 'uid' )
674          useclass = v.get( 'useClass' )
675
676          self.tt1[k].label = k
677          self.tt1[k].title = t
678          self.tt1[k].id = i
679          self.tt1[k].uid = uid
680          self.tt1[k].useClass = useclass
681          self.tableClasses[k]._h = self.tt1[k]
682          il = v.findall( '%sitem' % self.ns )
683          self.info( '%s, %s, %s, %s' % ( k, t, i, len(il) ) )
684 
685          self.tables[k] = (i,t,len(il))
686       
687          for i in il:
688            ii = self.tableClasses[k](xmlMiniDom=i, etree=True)
689            self.tableItems[k].append( ii )
690        elif len(vl) > 1:
691          assert False, 'not able to handle repeat sections with etree yet'
692      else:
693        vl = self.contentDoc.getElementsByTagName( k )
694        if len(vl) == 1:
695          v = vl[0]
696          t = v.getAttribute( 'title' )
697          i = v.getAttribute( 'id' )
698          il = v.getElementsByTagName( 'item' )
699          self.info( '%s, %s, %s, %s' % ( k, t, i, len(il) ) )
700 
701          self.tables[k] = (i,t,len(il))
702       
703          for i in il:
704            ii = self.tableClasses[k](xmlMiniDom=i)
705            self.tableItems[k].append( ii )
706        elif len(vl) > 1:
707          l1 = []
708          l2 = []
709          for v in vl:
710            t = v.getAttribute( 'title' )
711            i = v.getAttribute( 'id' )
712            il = v.getElementsByTagName( 'item' )
713            self.info( '%s, %s, %s, %s' % ( k, t, i, len(il) ) )
714            l1.append( (i,t,len(il)) )
715         
716            l2i = []
717            for i in il:
718              ii = self.tableClasses[k](xmlMiniDom=i)
719              l2i.append( ii )
720            l2.append( l2i )
721          self.tables[k] = l1
722          self.tableItems[k] = l2
723      self.coll[k] = self.ntf( self.recordAttributeDefn[k].header, self.recordAttributeDefn[k].attributes, self.tableItems[k] )
724 
725  def info(self,ss):
726    """Switchable print function ... switch off by setting self.silent=True"""
727    if not self.silent:
728      print ( ss )
729
730  def parsevcfg(self,v):
731      """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."""
732      if v in [ None,'__main__']:
733        idict = {'description':'An extended description of the object', 'title':'Record Description', \
734           'techNote':'', 'useClass':'__core__', 'superclass':'rdf:property',\
735           'type':'xs:string', 'uid':'__core__:description', 'label':'label', 'required':'required' }
736        if v == None:
737          vtt = self.nts( '__core__', 'CoreAttributes', 'X.1 Core Attributes', '00000000', 'def', '0', '0', 'false', '__core__' )
738        else:
739          vtt = self.nts( '__main__', 'DataRequestAttributes', 'X.2 Data Request Attributes', '00000001', 'def', '0', '0', 'false', '__main__' )
740      elif v == '__sect__':
741        idict = {'title':'Record Description', \
742         'uid':'__core__:description', 'label':'label', 'useClass':'text', 'id':'id', 'maxOccurs':'', 'itemLabelMode':'', 'level':'', 'labUnique':'' }
743        vtt = self.nts( '__sect__', 'sectionAttributes', 'X.3 Section Attributes', '00000000', 'def', '0', '0', 'false', '__sect__' )
744##<var label="var" uid="SECTION:var" useClass="vocab" title="MIP Variable" id="cmip.drv.001">
745      else:
746        l = v.getAttribute( 'label' )
747        t = v.getAttribute( 'title' )
748        i = v.getAttribute( 'id' )
749        ilm = v.getAttribute( 'itemLabelMode' )
750        lev = v.getAttribute( 'level' )
751        maxo = v.getAttribute( 'maxOccurs' )
752        labu = v.getAttribute( 'labUnique' )
753        il = v.getElementsByTagName( 'rowAttribute' )
754        vtt = self.nts( v.nodeName, l,t,i,ilm,lev, maxo, labu, 's__%s' % v.nodeName )
755        idict = {}
756        for i in il:
757          tt = self.parseicfg(i)
758          idict[tt.label] = tt
759      deflt = self.nt__default( {}, '__unset__' )
760
761      ## return a named tuple: (header, attributes, defaults)
762      return self.ntt( vtt, idict, deflt )
763
764  def parseicfg(self,i):
765      """Parse a record attribute specification"""
766      defs = {'type':"xs:string"}
767      ll = []
768      ee = {}
769      for k in ['label','title','type','useClass','techNote','description','uid','required']:
770        if i.hasAttribute( k ):
771          ll.append( i.getAttribute( k ) )
772        else:
773          ll.append( defs.get( k, None ) )
774        ee[k] = ll[-1]
775      l, t, ty, cls, tn, desc, uid, rq = ll
776      self.lastTitle = t
777      if rq in ['0', 'false']:
778        rq = False
779      else:
780        rq = True
781      ee['required'] = rq
782
783      returnClass = True
784      if returnClass:
785        return self._tableClass0( idict=ee )
786      else:
787        return self.nti( i.nodeName, l,t,ty,cls,tn,rq )
788
789class container(object):
790  """Simple container class, to hold a set of dictionaries of lists."""
791  def __init__(self, atl ):
792    self.uid = {}
793    for a in atl:
794      self.__dict__[a] =  collections.defaultdict( list )
795
796class c1(object):
797  def __init__(self):
798    self.a = collections.defaultdict( list )
799
800class index(object):
801  """Create an index of the document. Cross-references are generated from attributes with class 'internalLink'.
802This version assumes that each record is identified by an "uid" attribute and that there is a "var" section.
803Invalid internal links are recorded in tme "missingIds" dictionary.
804For any record, with identifier u, iref_by_uid[u] gives a list of the section and identifier of records linking to that record.
805"""
806
807  def __init__(self, dreq,lock=True):
808    self.silent = True
809    self.uid = {}
810    self.uid2 = collections.defaultdict( list )
811    nativeAtts = ['uid','iref_by_uid','iref_by_sect','missingIds']
812    naok = map( lambda x: not x in dreq, nativeAtts )
813    assert all(naok), 'This version cannot index collections containing sections with names: %s' % str( nativeAtts )
814    self.var_uid = {}
815    self.var_by_name = collections.defaultdict( list )
816    self.var_by_sn = collections.defaultdict( list )
817    self.iref_by_uid = collections.defaultdict( list )
818    irefdict = collections.defaultdict( list )
819    for k in dreq.keys():
820      if 'sn' in dreq[k].attDefn:
821         self.__dict__[k] =  container( ['label','sn'] )
822      else:
823         self.__dict__[k] =  container( ['label'] )
824    ##
825    ## collected names of attributes which carry internal links
826    ##
827      for ka in dreq[k].attDefn.keys():
828        if hasattr( dreq[k].attDefn[ka], 'useClass') and dreq[k].attDefn[ka].useClass in  ['internalLink','internalLinkList']:
829           irefdict[k].append( ka )
830
831    for k in dreq.keys():
832        for i in dreq[k].items:
833          assert 'uid' in i.__dict__, 'uid not found::\n%s\n%s' % (str(i._h),str(i.__dict__) )
834          if 'uid' in self.uid:
835            print ( 'ERROR.100.0001: Duplicate uid: %s [%s]' % (i.uid,i._h.title) )
836            self.uid2[i.uid].append( (k,i) )
837          else:
838### create index bx uid.
839            self.uid[i.uid] = i
840
841    self.missingIds = collections.defaultdict( list )
842    self.iref_by_sect = collections.defaultdict( c1 )
843    for k in dreq.keys():
844        for k2 in irefdict.get( k, [] ):
845          n1 = 0
846          n2 = 0
847          for i in dreq[k].items:
848            if k2 in i.__dict__:
849              id2 = i.__dict__.get( k2 )
850              if id2 != '__unset__':
851                sect = i._h.label
852  ## append attribute name and target  -- item i.uid, attribute k2 reference item id2
853                if type(id2) != type( [] ):
854                  id2 = [id2,]
855                for u in id2:
856                  self.iref_by_uid[ u ].append( (k2,i.uid) )
857                  self.iref_by_sect[ u ].a[sect].append( i.uid )
858                  if u in self.uid:
859                    n1 += 1
860                  else:
861                    n2 += 1
862                    self.missingIds[u].append( (k,k2,i.uid) )
863          self.info(  'INFO:: %s, %s%s (%s)' % (k,k2,n1,n2) )
864
865    for k in dreq.keys():
866      for i in dreq[k].items:
867        self.__dict__[k].uid[i.uid] = i
868        self.__dict__[k].label[i.label].append( i.uid )
869        if 'sn' in dreq[k].attDefn:
870          self.__dict__[k].sn[i.sn].append( i.uid )
871
872    if lock:
873      for k in self.iref_by_uid: 
874         self.iref_by_uid[k] = tuple( self.iref_by_uid[k] )
875      for k in self.iref_by_sect:
876        for s in self.iref_by_sect[ k ].a:
877          self.iref_by_sect[ k ].a[s] = tuple( self.iref_by_sect[ k ].a[s] )
878
879  def info(self,ss):
880    if not self.silent:
881      print ( ss )
882
883class ds(object):
884  """Comparison object to assist sorting of lists of dictionaries"""
885  def __init__(self,k):
886    self.k = k
887  def cmp(self,x,y):
888    return cmp( x.__dict__[self.k], y.__dict__[self.k] )
889
890class kscl(object):
891  """Comparison object to assist sorting of dictionaries of class instances"""
892  def __init__(self,idict,k):
893    self.k = k
894    self.idict = idict
895  def cmp(self,x,y):
896    return cmp( self.idict[x].__dict__[self.k], self.idict[y].__dict__[self.k] )
897
898src1 = '../workbook/trial_20150831.xml'
899
900#DEFAULT LOCATION -- changed automatically when building distribution
901defaultDreq = 'annotated_20150731.xml'
902#DEFAULT CONFIG
903defaultConfig = 'dreq2Defn.xml'
904
905defaultDreqPath = '%s/%s' % (DOC_DIR, defaultDreq )
906defaultConfigPath = '%s/%s' % (DOC_DIR, defaultConfig )
907
908class loadDreq(object):
909  """Load in a vocabulary document.
910  dreqXML: full path to the XML document
911  configdoc: full path to associated configuration document
912  useShelve: flag to specify whether to retrieve data from cache (not implemented)
913  htmlStyles: dictionary of styling directives which influence structure of html page generates by the "makeHtml" method
914"""
915
916  def __init__(self,dreqXML=defaultDreqPath, configdoc=defaultConfigPath, useShelve=False, htmlStyles=None, strings=False, manifest=None ):
917    self.c = config( thisdoc=dreqXML, configdoc=configdoc, useShelve=useShelve,strings=strings,manifest=manifest)
918    self.coll = self.c.coll
919    self.version = self.c.version
920    self.inx = index(self.coll)
921    self.itemStyles = {}
922    self.defaultItemLineStyle = lambda i, frm='', ann='': '<li>%s: %s</li>' % ( i.label, i.__href__(odir='../u/') )
923    self.softwareVersion = version
924##
925## add index to Item base class .. so that it can be accessed by item instances
926##
927    dreqItemBase._inx = self.inx
928    dreqItemBase._indexInitialised = True
929##
930## load in additional styling directives
931##
932    if htmlStyles != None:
933      for k in htmlStyles:
934        dreqItemBase._htmlStyle[k] = htmlStyles[k]
935
936##    dreqItemBase._htmlStyle['__general__'] = {'addRemarks':True}
937
938    self.pageTmpl = """<html><head><title>%s</title>
939%s
940<link rel="stylesheet" type="text/css" href="%scss/dreq.css">
941</head><body>
942
943<div id="top">
944   <div id="corner"></div>
945   CMIP6 Data Request
946</div>
947<div id="nav"><div><a href="%s" title="Home">Home</a></div></div>
948
949<div id="section">
950%s
951</div>
952</body></html>"""
953
954  def getHtmlItemStyle(self, sect):
955    """Get the styling method associated with a given section."""
956    if sect in self.itemStyles:
957      return self.itemStyles[sect]
958    return self.defaultItemLineStyle
959
960  def updateByUid( self, uid, dd, delete=[] ):
961    typePar={'xs:string':'x', 'xs:integer':0, 'xs:float':1., 'xs:boolean':True, 'xs:duration':'x'}
962    listTypePar={ "aa:st__integerList":1,"aa:st__integerListMonInc":1, "aa:st__stringList":'x', "aa:st__floatList":1. }
963
964    mmm = self.c.getByUid( uid )
965    assert len(mmm) == 1, 'Expected single uid match, found: %s' % len(mmm)
966    thisdoc = mmm[0]
967    item = self.inx.uid[uid]
968    d1 = [d for d in delete if d not in item._a]
969    e1 = len(d1) > 0
970    if e1:
971      print ('ERROR.update.0001: request to delete non-existent keys: %s' % d1 )
972    d1 = [d for d in delete if d in item._a and item._a[d].required ]
973    e1b = len(d1) > 0
974    if e1b:
975      print ('ERROR.update.0002: request to delete required attributes: %s' % d1 )
976    d2 = [d for d in dd if d not in item._a]
977    e2 = len(d2) > 0
978    if e2:
979      print ('ERROR.update.0003: request to modify non-existent keys: %s' % d2 )
980    e3 = []
981    for k in [d for d in dd if d in item._a]:
982      if item._a[k].type in typePar:
983        e3.append( type( dd[k] ) != type( typePar[item._a[k].type] ) )
984        if e3[-1]:
985          print ('ERROR.update.0004: value has wrong type [%s] %s --- %s' % (item._a[k].type, dd[k], type( dd[k] ) ) )
986      elif item._a[k].type in listTypePar:
987        a = type( dd[k] ) not in [type( [] ), type( () )]
988        if a:
989          print ('ERROR.update.0005: value has wrong type [%s] %s --- should be list or tuple' % (item._a[k].type, dd[k] ) )
990        else:
991          a = not all( [type(x) == type(listTypePar[item._a[k].type]) for x in dd[k]] )
992          if a:
993            print ('ERROR.update.0005: value has wrong type [%s] %s --- %s' % (item._a[k].type, dd[k], [type(x)for x in dd[k]] ) )
994
995        if not a and item._a[k].type == 'aa:st__integerListMonInc':
996          a = not all( [dd[k][i+1] > dd[k][i] for i in range( len(dd[k]) -1 )] )
997          if a:
998            print ('ERROR.update.0006: value should be a monotonic increasing integer list: %s' % str( dd[k] ) )
999        e3.append(a)
1000      else:
1001        print ( 'Type not recognised: %s' % item._a[k].type )
1002        e3.append( False )
1003    eee = any([e1,e1b,e2,any(e3)])
1004    assert not eee, 'STOPPING: validation errors'
1005    self.thisdoc = thisdoc
1006    for k in dd:
1007      thisdoc.set( k, self.__string4xml__( dd[k], item._a[k].type ) )
1008      item.__dict__[k] = dd[k]
1009
1010  def saveXml( self, docfn=None, targ=None ):
1011    if docfn == None:
1012      docfn, tt = self.c.docs.items()[0]
1013    else:
1014      tt = self.c.docs[docfn]
1015
1016    if targ == None:
1017      targ = docfn
1018    tt[1].write( targ )
1019    print ('XML document saved to %s' % targ)
1020 
1021  def __string4xml__(self,v,typ):
1022    if typ in ['xs:string','xs:duration']:
1023       return v
1024    elif typ in ['xs:integer', 'xs:float', 'xs:boolean']:
1025       return str(v)
1026    elif typ == "aa:st__stringList":
1027       return string.join(v)
1028    elif typ in ["aa:st__integerList","aa:st__integerListMonInc", "aa:st__floatList"]:
1029       return string.join( [str(x) for x in v] )
1030    else:
1031       assert False, 'Data type not recognised'
1032     
1033  def _sectionSortHelper(self,title):
1034
1035    ab = title.split(  )[0].split('.')
1036    if len( ab ) == 2:
1037      a,b = ab
1038
1039      if self.c.rc.isIntStr(a):
1040        a = int(a)
1041      if self.c.rc.isIntStr(b):
1042        b = int(b)
1043      rv = (a,b)
1044    elif len(ab) == 1:
1045      rv = (ab[0],0)
1046    else:
1047      rv = ab
1048    return rv
1049
1050  def makeHtml(self,odir='./html', ttl0 = 'Data Request Index', annotations=None):
1051    """Generate a html view of the vocabularies, using the "__html__" method of the vocabulary item class to generate a
1052page for each item and also generating index pages.
1053    odir: directory for html files;
1054    ttl0: Title for main index (in odir/index.html)"""
1055
1056    ks = self.inx.uid.keys()
1057    ks.sort( kscl( self.inx.uid, 'title' ).cmp )
1058    for k in ks:
1059      i = self.inx.uid[k]
1060      ttl = 'Data Request Record: [%s]%s' % (i._h.label,i.label)
1061      bdy = string.join( i.__html__( ghis=self.getHtmlItemStyle ), '\n' )
1062      oo = open( '%s/u/%s.html' % (odir,i.uid), 'w' )
1063      oo.write( self.pageTmpl % (ttl, jsh, '../', '../index.html', bdy ) )
1064      oo.close()
1065
1066    msg0 = ['<h1>%s</h1>' % ttl0, '<ul>',]
1067    msg0.append( '<li><a href="tab01_1_1.html">Overview: priority 1 variables, tier 1 experiments</a></li>' )
1068    msg0.append( '<li><a href="tab01_3_3.html">Overview: all variables and experiments</a></li>' )
1069    ks = sorted( self.coll.keys() )
1070    ee = {}
1071    for k in ks:
1072      ee[self.coll[k].header.title] = k
1073    kks = sorted( ee.keys(),  key = self._sectionSortHelper )
1074    for kt in kks:
1075      k = ee[kt]
1076##
1077## sort on item label
1078##
1079      if annotations != None and k in annotations:
1080        ann = annotations[k]
1081      else:
1082        ann = {}
1083
1084      self.coll[k].items.sort( ds('label').cmp )
1085      ttl = 'Data Request Section: %s' % k
1086      msg0.append( '<li><a href="index/%s.html">%s [%s]</a></li>\n' % (k,self.coll[k].header.title,k) )
1087      msg = ['<h1>%s</h1>\n' % ttl, '<ul>',]
1088      msg.append( '<a href="../index.html">Home</a><br/>\n' )
1089      lst = self.getHtmlItemStyle(k)
1090     
1091      for i in self.coll[k].items:
1092        ##m = '<li>%s: %s</li>' % ( i.label, i.__href__(odir='../u/') )
1093       
1094        m = lst( i, ann=ann.get( i.label ) )
1095        msg.append( m )
1096      msg.append( '</ul>' )
1097      bdy = string.join( msg, '\n' )
1098      oo = open( '%s/index/%s.html' % (odir,k), 'w' )
1099      oo.write( self.pageTmpl % (ttl, '', '../', '../index.html', bdy ) )
1100      oo.close()
1101    msg0.append( '</ul>' )
1102    bdy = string.join( msg0, '\n' )
1103    oo = open( '%s/index.html' % odir, 'w' )
1104    oo.write( self.pageTmpl % (ttl0, '', '', 'index.html', bdy ) )
1105    oo.close()
1106   
1107if __name__ == '__main__':
1108  dreq = loadDreq(manifest='out/dreqManifest.txt' )
1109
Note: See TracBrowser for help on using the repository browser.