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

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

debugging commandline options -- removing hidden settings

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