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

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

cleaned up imports to re-enable python 3

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