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

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

01.00.07 ... prep

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