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

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

cleaned overview tabs

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