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

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

python3 enabled makeTables

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