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

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

charme enabled

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