1 | #!/usr/bin/env python |
---|
2 | # |
---|
3 | # This python code (will) handle all forms of stub-B and provide HTML |
---|
4 | # methods to the extent required by the NDG browse functionality. |
---|
5 | # NB, It would be cleaner to use Dom's way of doing things but we'd |
---|
6 | # need a schema for that ... |
---|
7 | # BNL April 2006 |
---|
8 | # |
---|
9 | |
---|
10 | from Utilities import * |
---|
11 | from BeautifulSoup import BeautifulSoup |
---|
12 | from AccessControl import AccessControl |
---|
13 | from geoUtilities import Bounding |
---|
14 | from People import * |
---|
15 | from ndgObject import ndgObject |
---|
16 | from ows_server.models import Utilities |
---|
17 | |
---|
18 | #from DeploymentHandling import * |
---|
19 | try: #python 2.5 |
---|
20 | from xml.etree import ElementTree as ET |
---|
21 | except ImportError: |
---|
22 | try: |
---|
23 | # if you've installed it yourself it comes this way |
---|
24 | import ElementTree as ET |
---|
25 | except ImportError: |
---|
26 | # if you've egged it this is the way it comes |
---|
27 | from elementtree import ElementTree as ET |
---|
28 | |
---|
29 | |
---|
30 | def collapse2(keywords,split='>'): |
---|
31 | ''' Take the last element of a DIF parameter tree entry, and put them in a dictionary |
---|
32 | under the DIF category ''' |
---|
33 | d={} |
---|
34 | for item in keywords: |
---|
35 | line=item.split(split) |
---|
36 | category=line[0] |
---|
37 | if category not in d: d[category]=[] |
---|
38 | while line<>[]: |
---|
39 | c=line.pop() |
---|
40 | if c<>'': |
---|
41 | if c<>category: |
---|
42 | d[category].append(c) |
---|
43 | line=[] |
---|
44 | return d |
---|
45 | |
---|
46 | |
---|
47 | def idconvert(helper,elem,config=None,idelem='dgMetadataID'): |
---|
48 | ''' Given an elementTree element for a dgMetadataID, create |
---|
49 | a standard ndg URI, and produce an ndgObject ''' |
---|
50 | id=helper.find(elem,idelem) |
---|
51 | entryID='%s__%s__%s'%(helper.getText(id,'repositoryIdentifier'), |
---|
52 | helper.getText(id,'schemeIdentifier'), |
---|
53 | helper.getText(id,'localIdentifier')) |
---|
54 | uri=ndgObject(entryID,config) |
---|
55 | return uri |
---|
56 | |
---|
57 | class ndgLink: |
---|
58 | ''' This is a holder for an ndgObject which has a name (and possibly abbreviation) ''' |
---|
59 | def __init__(self,name,abbrev,ndgO,label='',image=None): |
---|
60 | self.ndgObject=ndgO |
---|
61 | self.name=name |
---|
62 | self.abbrev=abbrev |
---|
63 | self.image=image |
---|
64 | self.label=label |
---|
65 | self.URL=ndgO.BURL |
---|
66 | def toHTML(self): |
---|
67 | ### needs to be finished |
---|
68 | return self.ndgObject.URL |
---|
69 | |
---|
70 | class dataGranule: |
---|
71 | ''' Provides support for data granule structures ''' |
---|
72 | def __init__(self,helper,elem,config,name=''): |
---|
73 | self.elem=elem |
---|
74 | self.constraints=AccessControl(helper.find(elem,'accessControlPolicy')) |
---|
75 | self.name=name |
---|
76 | self.uri=idconvert(helper,elem,config,idelem='dataModelID') |
---|
77 | self.entryID=self.uri.uri |
---|
78 | self.bbox=Bounding(self.elem,helper,entity='moles') |
---|
79 | self.timeCoverage=temporal(self.elem,helper) |
---|
80 | self.parameters=helper.getText(elem,'dgGranuleSummary/dgParameterSummary/ParameterName',multiple=1) |
---|
81 | |
---|
82 | class SelectedItem: |
---|
83 | ''' A simple object representing key info on selected items ''' |
---|
84 | def __init__(self,entryID,title,kmlURL,wmcURL): |
---|
85 | self.entryID = entryID |
---|
86 | self.title = title |
---|
87 | self.kmlURL = kmlURL |
---|
88 | self.wmcURL = wmcURL |
---|
89 | self.kmlList = Utilities.recreateListFromUnicode(kmlURL) |
---|
90 | self.wmcList = Utilities.recreateListFromUnicode(wmcURL) |
---|
91 | |
---|
92 | class ObservationStation: |
---|
93 | def __init__(self,h,e): |
---|
94 | self.e=e |
---|
95 | self.hdg='Observation Station' |
---|
96 | if e is None: return |
---|
97 | class DataProductionTool: |
---|
98 | def __init__(self,h,e): |
---|
99 | self.e=e |
---|
100 | i=h.find(e,'dgInstrument') |
---|
101 | if i is not None: |
---|
102 | self.hdg='Instrument' |
---|
103 | else: |
---|
104 | self.hdg='Model' |
---|
105 | if e is None: return |
---|
106 | class Activity: |
---|
107 | def __init__(self,h,e): |
---|
108 | self.e=e |
---|
109 | self.hdg='Activity' |
---|
110 | if e is None: return |
---|
111 | |
---|
112 | def Deployments(elem,helper,config): |
---|
113 | ''' This is the late June 2007 version ''' |
---|
114 | if elem is None: return None |
---|
115 | types={'dptList':'dataproductiontool','activityList':'activity','obsStnList':'observationstation','dataEntityList':'dataentity'} |
---|
116 | names={'dptList':'Data Production Tools','activityList':'Activities','obsStnList':'Observation Stations','dataEntityList':'Data Entities'} |
---|
117 | result={} |
---|
118 | for alist in types: |
---|
119 | blist=helper.find(elem,alist) |
---|
120 | collection=[] |
---|
121 | if blist is not None: |
---|
122 | for object in blist: |
---|
123 | uri=idconvert(helper,object,config=config) |
---|
124 | name=helper.getText(object,'name') |
---|
125 | shortName=helper.getText(object,'abbreviation') |
---|
126 | collection.append((uri,name,shortName)) |
---|
127 | result[names[alist]]=collection |
---|
128 | return result |
---|
129 | |
---|
130 | def temporal(elem,helper): |
---|
131 | ''' Parse for time coverage attributes ''' |
---|
132 | timeElems=helper.findall(elem,'*//dgTemporalCoverage') |
---|
133 | tc=[] |
---|
134 | for e in timeElems: |
---|
135 | t=e.find('DateRange') |
---|
136 | if t is not None: |
---|
137 | c=[helper.getText(t,'DateRangeStart'),] |
---|
138 | c.append(helper.getText(t,'DateRangeEnd')) |
---|
139 | else: |
---|
140 | c=[helper.getText(e,'DateSingle'),''] |
---|
141 | c.append(helper.getText(elem,'*/dgDatasetStatus/dgDatasetClosure')) |
---|
142 | tc.append(c) |
---|
143 | return tc |
---|
144 | |
---|
145 | def parseParameters(plist,helper): |
---|
146 | ''' Takes a list of ET Parameter Summary elements, and parses them into a |
---|
147 | sensible pythonic structure. In particular, given: |
---|
148 | <dgParameterSummary> |
---|
149 | <IsOutput>true</IsOutput> |
---|
150 | <dgStdParameterMeasured> |
---|
151 | <dgValidTerm>BAROCLINIC V_VELOCITY (OCEAN) CM/S</dgValidTerm> |
---|
152 | <dgValidTermID> |
---|
153 | <ParentListID>COAPEC_500YrRun_wholerun_decadal_ocean</ParentListID> |
---|
154 | <TermID>null</TermID> |
---|
155 | </dgValidTermID> |
---|
156 | </dgStdParameterMeasured> |
---|
157 | <ParameterName>BAROCLINIC V_VELOCITY (OCEAN) CM/S</ParameterName> |
---|
158 | </dgParameterSummary> |
---|
159 | we take the parameter name and the parentlistID and use them to build up a |
---|
160 | parameter dictionary ''' |
---|
161 | ptypes={} |
---|
162 | for p in plist: |
---|
163 | ptype=helper.getText(p,'dgStdParameterMeasured/dgValidTermID/ParentListID') |
---|
164 | if ptype not in ptypes: ptypes[ptype]=[] |
---|
165 | ptypes[ptype].append(helper.getText(p,'ParameterName')) |
---|
166 | gcmd='http://vocab.ndg.nerc.ac.uk/term/P111' |
---|
167 | if gcmd in ptypes: |
---|
168 | dif=collapse2(ptypes[gcmd],split='/') |
---|
169 | del ptypes[gcmd] |
---|
170 | return dif,ptypes |
---|
171 | |
---|
172 | class DataEntity: |
---|
173 | def __init__(self,helper,element): |
---|
174 | ''' Attempt to instantiate this stubB as a data entity''' |
---|
175 | self.elem=element |
---|
176 | self.helper=helper |
---|
177 | #just one curator |
---|
178 | self.curator=dgContact(self.elem.find('dgDataRoles/dgDataCurator'),ctype='organisation') |
---|
179 | #possibly multiple creators |
---|
180 | self.creators=[dgContact(i) for i in self.elem.findall('dgDataRoles/dgDataCreator')] |
---|
181 | host,service = '','' # dummy for now |
---|
182 | #possibly multiple granules |
---|
183 | self.getGranules() |
---|
184 | #bounding box, handled as a class because this is going to be difficult ... |
---|
185 | self.bbox=Bounding(self.elem,helper,entity='moles') |
---|
186 | self.timeCoverage=temporal(self.elem,self.helper) |
---|
187 | #parameters |
---|
188 | pelem=self.helper.findall(self.elem,'dgDataSummary/dgParameterSummary') |
---|
189 | self.parameters,self.extraParameters=parseParameters(pelem,self.helper) |
---|
190 | self.hdg='Data Entity' |
---|
191 | |
---|
192 | def getGranules(self): |
---|
193 | ''' Load up the granule content within the entity ''' |
---|
194 | granList=self.elem.findall('dgDataGranule') |
---|
195 | self.granules=[] |
---|
196 | i=0 |
---|
197 | for item in granList: |
---|
198 | i+=1 |
---|
199 | #following needs to be refactored when granule definition includes a proper name ... |
---|
200 | name=wrapGetText(item,'dgGranuleSummary/dgGranuleName') |
---|
201 | if name=='': name='Granule %s'%i |
---|
202 | self.granules.append(dataGranule(self.helper,item,self.config,name=name)) |
---|
203 | |
---|
204 | class dgMetadataDescription: |
---|
205 | |
---|
206 | def __init__(self,helper,elem,viewService): |
---|
207 | ''' Initialise a Metadata Description ''' |
---|
208 | n=idconvert(helper,elem) |
---|
209 | self.uri=n.uri |
---|
210 | self.logo=None |
---|
211 | self.abstract=helper.getText(elem,'abstract/abstractText') |
---|
212 | self.texts=[] |
---|
213 | self.onlineRefs=[] |
---|
214 | for e in helper.findall(elem,'descriptionSection'): |
---|
215 | textElem=helper.find(e,'dgDescriptionText') |
---|
216 | if textElem is not None: |
---|
217 | text=(textElem.text or '') |
---|
218 | tail='' |
---|
219 | for i in textElem: |
---|
220 | text+=ET.tostring(i) |
---|
221 | tail=(i.tail or '') |
---|
222 | text+=tail |
---|
223 | ctype=helper.getText(e,'contentType') |
---|
224 | if ctype in ['','text/html']: |
---|
225 | soup=BeautifulSoup(text) |
---|
226 | text=soup.prettify() |
---|
227 | self.texts.append((text,ctype)) |
---|
228 | online=helper.findall(elem,'descriptionSection/descriptionOnlineReference/dgSimpleLink') |
---|
229 | for o in online: |
---|
230 | rtype=helper.getText(o,'name') |
---|
231 | #url and uri supported in 1.4 moles, but no one has implemented that yet ... |
---|
232 | uri,url=helper.getText(o,'URI'),helper.getText(o,'URL') |
---|
233 | if rtype=='Logo': |
---|
234 | self.logo=uri |
---|
235 | else: |
---|
236 | try: |
---|
237 | value=help |
---|
238 | self.onlineRefs.append((rtype,{'NumSim':'%s/%s'%(viewService,uri)}[rtype])) |
---|
239 | except KeyError: |
---|
240 | self.onlineRefs.append((rtype,uri)) |
---|
241 | |
---|
242 | |
---|
243 | class stubB(DataEntity,ObservationStation,DataProductionTool,Activity): |
---|
244 | |
---|
245 | ''' Holds the stub-b document and provides methods which get and manipulate it ''' |
---|
246 | |
---|
247 | def __init__(self,elem,config): |
---|
248 | |
---|
249 | '''Instantiate with an element tree elem ''' |
---|
250 | |
---|
251 | self.tree=elem |
---|
252 | self.metadataType='NDG-B1' |
---|
253 | self.services=[] |
---|
254 | self.config=config |
---|
255 | self.citation='' |
---|
256 | self.personnel=[] # for DIF compatiability for the moment. |
---|
257 | self.parameters={} |
---|
258 | self.extraParameters={} |
---|
259 | self.granules=[] |
---|
260 | self.logos=[] |
---|
261 | |
---|
262 | try: |
---|
263 | helper=nsdumb(self.tree) |
---|
264 | self.name=helper.getText(self.tree,'name') |
---|
265 | except Exception,e: |
---|
266 | raise ValueError('Error instantiating stubB [%s]'%e) |
---|
267 | |
---|
268 | self.ndgObject=idconvert(helper,self.tree,self.config) |
---|
269 | self.entryID=self.ndgObject.uri |
---|
270 | self.viewService=self.ndgObject.viewService |
---|
271 | |
---|
272 | #Note that the root of the ElementTree instance is dgMetadataRecord |
---|
273 | #so we don't need (or want) that in our xpath expressions. |
---|
274 | |
---|
275 | self.constraints=AccessControl(helper.find(self.tree,'dgMetadataSecurity')) |
---|
276 | self.abbreviation=helper.getText(self.tree,'abbreviation') |
---|
277 | if self.abbreviation=='': self.abbreviation=self.name[0:min(15,len(self.name))] |
---|
278 | logos=helper.findall(self.tree,'dgMetadata/dgMetadatdataRecord/logos/logoURI') |
---|
279 | for l in logos: |
---|
280 | self.logos.append((helper.find(l,'dgSimpleLink/name'), |
---|
281 | helper.find(l,'dgSimpleLink/URI'))) |
---|
282 | |
---|
283 | for i in ('dgDataEntity','dgActivity','dgDataProductionTool','dgObservationStation'): |
---|
284 | elem=helper.find(self.tree,i) |
---|
285 | if elem is not None: break |
---|
286 | if elem is None: |
---|
287 | raise ValueError('StubB record does not contain an Activity, DE, DPT, ObsStn') |
---|
288 | elif elem.tag=='dgDataEntity': |
---|
289 | DataEntity.__init__(self,helper,elem) |
---|
290 | elif elem.tag=='dgActivity': |
---|
291 | Activity.__init__(self,helper,elem) |
---|
292 | elif elem.tag=='dgDataProductionTool': |
---|
293 | DataProductionTool.__init__(self,helper,elem) |
---|
294 | elif elem.tag=='dgObservationStation': |
---|
295 | ObservationStation.__init__(self,helper,elem) |
---|
296 | |
---|
297 | self.description=dgMetadataDescription( |
---|
298 | helper,helper.find(self.tree,'dgMetadataDescription'),self.viewService) |
---|
299 | if self.description.logo is not None: |
---|
300 | self.logos.append((('Logo',self.description.logo))) |
---|
301 | print 'LOGOS',self.logos |
---|
302 | self.abstract=self.description.abstract |
---|
303 | self.stubBtype=elem.tag |
---|
304 | |
---|
305 | # now go get all the related links |
---|
306 | |
---|
307 | self.related=Deployments(helper.find(elem,'DeploymentSummary'),helper,self.config) |
---|
308 | |
---|
309 | |
---|
310 | if __name__=="__main__": |
---|
311 | import unittest |
---|
312 | import os.path |
---|
313 | from ndgRetrieve import ndgRetrieve |
---|
314 | |
---|
315 | |
---|
316 | class TestCase(unittest.TestCase): |
---|
317 | |
---|
318 | def testDE(self): |
---|
319 | ''' Test rendering a DataEntity stubB ''' |
---|
320 | doc='badc.nerc.ac.uk__NDG-B1__dataent_COAPEC' |
---|
321 | xml=self.getit(doc) |
---|
322 | self.doit(xml.tree) |
---|
323 | |
---|
324 | def getit(self,doc): |
---|
325 | self.c=myConfig('../../ndgDiscovery.config') |
---|
326 | uri=ndgObject(doc) |
---|
327 | status,xml=ndgRetrieve(uri,self.c) |
---|
328 | self.assertEqual(status,1) |
---|
329 | return xml |
---|
330 | |
---|
331 | def doit(self,xml): |
---|
332 | layoutdir=self.c.get('layout','layoutdir') |
---|
333 | x=stubB(xml,self.c) |
---|
334 | |
---|
335 | |
---|
336 | unittest.main() |
---|
337 | |
---|
338 | |
---|