root/OWSLib/trunk/owslib/csw.py

Revision 1649, 21.1 KB (checked in by tomkralidis, 3 months ago)

sort namespaces dict

Line 
1#!/usr/bin/python
2# -*- coding: ISO-8859-15 -*-
3# =============================================================================
4# Copyright (c) 2009 Tom Kralidis
5#
6# Authors : Tom Kralidis <tomkralidis@hotmail.com>
7#
8# Contact email: tomkralidis@hotmail.com
9# =============================================================================
10
11""" CSW request and response processor """
12
13import StringIO
14import random
15from owslib.etree import etree
16from owslib.filter import *
17from owslib import util
18from owslib.ows import *
19from owslib.iso import *
20from owslib.fgdc import *
21from owslib.dif import *
22
23# default variables
24
25outputformat = 'application/xml'
26schema = 'http://schemas.opengis.net/csw/2.0.2/CSW-discovery.xsd'
27
28namespaces = {
29    None : 'http://www.opengis.net/cat/csw/2.0.2',
30    'csw': 'http://www.opengis.net/cat/csw/2.0.2',
31    'dc' : 'http://purl.org/dc/elements/1.1/',
32    'dct': 'http://purl.org/dc/terms/',
33    'dif': 'http://gcmd.gsfc.nasa.gov/Aboutus/xml/dif/',
34    'fgdc': 'http://www.fgdc.gov',
35    'gco': 'http://www.isotc211.org/2005/gco',
36    'gmd': 'http://www.isotc211.org/2005/gmd',
37    'gml': 'http://www.opengis.net/gml',
38    'ogc': 'http://www.opengis.net/ogc',
39    'ows': 'http://www.opengis.net/ows',
40    'rim': 'urn:oasis:names:tc:ebxml-regrep:xsd:rim:3.0',
41    'xs' : 'http://www.w3.org/2001/XMLSchema',
42    'xs2': 'http://www.w3.org/XML/Schema',
43    'xsi': 'http://www.w3.org/2001/XMLSchema-instance'
44}
45
46schema_location = '%s %s' % (namespaces['csw'], schema)
47
48class CatalogueServiceWeb:
49    """ csw request class """
50    def __init__(self, url, lang='en-US', version='2.0.2'):
51        """
52
53        Construct and process a GetCapabilities request
54
55        Parameters
56        ----------
57
58        - url: the URL of the CSW
59        - lang: the language (default is 'en-US')
60        - version: version (default is '2.0.2')
61
62        """
63
64        self.url = url
65        self.lang = lang
66        self.version = version
67        self.service = 'CSW'
68        self.exceptionreport = None
69        self.owscommon = OwsCommon('1.0.0')
70
71        # construct request
72        node0 = etree.Element(util.nspath('GetCapabilities', namespaces['csw']))
73        node0.set('service', self.service)
74        node0.set(util.nspath('schemaLocation', namespaces['xsi']), schema_location)
75        tmp = etree.SubElement(node0, util.nspath('AcceptVersions', namespaces['ows']))
76        etree.SubElement(tmp, util.nspath('Version', namespaces['ows'])).text = self.version
77        tmp2 = etree.SubElement(node0, util.nspath('AcceptFormats', namespaces['ows']))
78        etree.SubElement(tmp2, util.nspath('OutputFormat', namespaces['ows'])).text = outputformat
79        self.request = util.xml2string(etree.tostring(node0))
80
81        # invoke
82        self.response = util.http_post(self.url, self.request, self.lang)
83
84        # parse result
85        self._capabilities = etree.parse(StringIO.StringIO(self.response))
86
87        # check for exceptions
88        self._isexception(self._capabilities, self.owscommon.namespace)
89
90        if self.exceptionreport is None:
91            # ServiceIdentification
92            val = self._capabilities.find(util.nspath('ServiceIdentification', namespaces['ows']))
93            self.identification=ServiceIdentification(val,self.owscommon.namespace)
94            # ServiceProvider
95            val = self._capabilities.find(util.nspath('ServiceProvider', namespaces['ows']))
96            self.provider=ServiceProvider(val,self.owscommon.namespace)
97            # ServiceOperations metadata
98            self.operations=[]
99            for elem in self._capabilities.findall(util.nspath('OperationsMetadata/Operation', namespaces['ows'])):
100                self.operations.append(OperationsMetadata(elem, self.owscommon.namespace))
101   
102            # FilterCapabilities
103            val = self._capabilities.find(util.nspath('Filter_Capabilities', namespaces['ogc']))
104            self.filters=FilterCapabilities(val)
105 
106    def describerecord(self, typename='csw:Record', format=outputformat):
107        """
108
109        Construct and process DescribeRecord request
110
111        Parameters
112        ----------
113
114        - typename: the typename to describe (default is 'csw:Record')
115        - format: the outputFormat (default is 'application/xml')
116 
117        """
118
119        # construct request
120        node0 = etree.Element(util.nspath('DescribeRecord', namespaces['csw']))
121        node0.set('service', self.service)
122        node0.set('version', self.version)
123        node0.set('outputFormat', format)
124        node0.set('schemaLanguage', namespaces['xs2'])
125        node0.set(util.nspath('schemaLocation', namespaces['xsi']), schema_location)
126        etree.SubElement(node0, util.nspath('TypeName', namespaces['csw'])).text = typename
127        self.request = util.xml2string(etree.tostring(node0))
128
129        # invoke
130        self.response = util.http_post(self.url, self.request, self.lang)
131
132        # parse result
133        # TODO: process the XML Schema (you're on your own for now with self.response)
134
135    def getdomain(self, dname, dtype='parameter'):
136        """
137
138        Construct and process a GetDomain request
139
140        Parameters
141        ----------
142
143        - dname: the value of the Parameter or Property to query
144        - dtype: whether to query a parameter (parameter) or property (property)
145
146        """
147
148        # construct request
149        dtypename = 'ParameterName'
150        node0 = etree.Element(util.nspath('GetDomain', namespaces['csw']))
151        node0.set('service', self.service)
152        node0.set('version', self.version)
153        node0.set(util.nspath('schemaLocation', namespaces['xsi']), schema_location)
154        if dtype == 'property':
155            dtypename = 'PropertyName'
156        etree.SubElement(node0, util.nspath(dtypename, namespaces['csw'])).text = dname
157        self.request = util.xml2string(etree.tostring(node0))
158
159        # invoke
160        self.response = util.http_post(self.url, self.request, self.lang)
161
162        # parse result
163        self._values = etree.parse(StringIO.StringIO(self.response))
164
165        # check for exceptions
166        self._isexception(self._values, self.owscommon.namespace)
167
168        if self.exceptionreport is None:
169            self.results = {}
170
171            val = self._values.find(util.nspath('DomainValues', namespaces['csw'])).attrib.get('type')
172            self.results['type'] = util.testXMLValue(val, True)
173
174            val = self._values.find(util.nspath('DomainValues/' + dtypename, namespaces['csw']))
175            self.results[dtype] = util.testXMLValue(val)
176
177            # get the list of values associated with the Domain
178            self.results['values'] = []
179
180            for f in self._values.findall(util.nspath('DomainValues/ListOfValues/Value', namespaces['csw'])):
181                self.results['values'].append(util.testXMLValue(f))
182
183    def getrecords(self, qtype=None, keywords=[], typenames='csw:Record', propertyname='AnyText', bbox=None, esn='full', sortby=None, outputschema=namespaces['csw'], format=outputformat, startposition=0, maxrecords=10):
184        """
185
186        Construct and process a  GetRecords request
187
188        Parameters
189        ----------
190
191        - qtype: type of resource to query (i.e. service, dataset)
192        - keywords: list of keywords
193        - typenames: the typeNames to query against (default is csw:Record)
194        - propertyname: the PropertyName to Filter against
195        - bbox: the bounding box of the spatial query in the form [minx,miny,maxx,maxy]
196        - esn: the ElementSetName 'full', 'brief' or 'summary' (default is 'full')
197        - sortby: property to sort results on (default is 'dc:title')
198        - outputschema: the outputSchema (default is 'http://www.opengis.net/cat/csw/2.0.2')
199        - format: the outputFormat (default is 'application/xml')
200        - startposition: requests a slice of the result set, starting at this position (default is 0)
201        - maxrecords: the maximum number of records to return. No records are returned if 0 (default is 10)
202
203        """
204
205        # construct request
206        node0 = etree.Element(util.nspath('GetRecords', namespaces['csw']))
207        node0.set('outputSchema', outputschema)
208        node0.set('outputFormat', format)
209        node0.set('version', self.version)
210        node0.set('resultType', 'results')
211        node0.set('service', self.service)
212        if startposition > 0:
213            node0.set('startPosition', str(startposition))
214        node0.set('maxRecords', str(maxrecords))
215        node0.set(util.nspath('schemaLocation', namespaces['xsi']), schema_location)
216
217        # decipher number of query parameters ( > 1 sets an 'And' Filter
218        pcount = 0
219        if qtype is not None:
220            pcount += 1
221        if keywords:
222            pcount += 1
223        if bbox is not None:
224            pcount += 1
225
226        node1 = etree.SubElement(node0, util.nspath('Query', namespaces['csw']))
227        node1.set('typeNames', typenames)
228   
229        etree.SubElement(node1, util.nspath('ElementSetName', namespaces['csw'])).text = esn
230
231        # decipher if the query is for real
232        if keywords or bbox is not None or qtype is not None:   
233            node2 = etree.SubElement(node1, util.nspath('Constraint', namespaces['csw']))
234            node2.set('version', '1.1.0')
235            node3 = etree.SubElement(node2, util.nspath('Filter', namespaces['ogc']))
236            node4 = None
237
238            # construct a Filter request
239            flt   = FilterRequest()   
240 
241            if pcount > 1: # Filter should be And-ed
242                node4 = etree.SubElement(node3, util.nspath('And', namespaces['ogc']))
243
244            # set the query type if passed
245            # TODO: need a smarter way to figure these out
246            if qtype is not None:
247                if node4 is not None:
248                    flt.setpropertyisequalto(node4, 'dc:type', qtype)
249                else:
250                    flt.setpropertyisequalto(node3, 'dc:type', qtype)
251
252            # set a bbox query if passed
253            if bbox is not None:
254                if node4 is not None:
255                    flt.setbbox(node4, bbox)
256                else:
257                    flt.setbbox(node3, bbox)
258
259            # set a keyword query if passed
260            if len(keywords) > 0:
261                if len(keywords) > 1: # loop multiple keywords into an Or
262                    if node4 is not None:
263                        node5 = etree.SubElement(node4, util.nspath('Or', namespaces['ogc']))
264                    else:
265                        node5 = etree.SubElement(node3, util.nspath('Or', namespaces['ogc']))
266
267                    for i in keywords:
268                        flt.setpropertyislike(node5, propertyname, '%%%s%%' % i)
269
270                else: # one keyword
271                    if node4 is not None:
272                        flt.setpropertyislike(node4, propertyname, '%%%s%%' % keywords[0])
273                    else:
274                        flt.setpropertyislike(node3, propertyname, '%%%s%%' % keywords[0])
275
276        # set a sort if passed
277        if sortby is not None:
278            flt.setsortby(node1, sortby)
279   
280        self.request = util.xml2string(etree.tostring(node0))
281
282        # invoke
283        self.response = util.http_post(self.url, self.request, self.lang)
284
285        # parse result
286        self._records = etree.parse(StringIO.StringIO(self.response))
287
288        # check for exceptions
289        self._isexception(self._records, self.owscommon.namespace)
290 
291        if self.exceptionreport is None:
292            self.results = {}
293   
294            # process search results attributes
295            val = self._records.find(util.nspath('SearchResults', namespaces['csw'])).attrib.get('numberOfRecordsMatched')
296            self.results['matches'] = int(util.testXMLValue(val, True))
297            val = self._records.find(util.nspath('SearchResults', namespaces['csw'])).attrib.get('numberOfRecordsReturned')
298            self.results['returned'] = int(util.testXMLValue(val, True))
299            val = self._records.find(util.nspath('SearchResults', namespaces['csw'])).attrib.get('nextRecord')
300            self.results['nextrecord'] = int(util.testXMLValue(val, True))
301   
302            # process list of matching records
303            self.records = {}
304
305            self._parserecords(outputschema, esn)
306
307    def getrecordbyid(self, id=[], esn='full', outputschema=namespaces['csw'], format=outputformat):
308        """
309
310        Construct and process a GetRecordById request
311
312        Parameters
313        ----------
314
315        - id: the list of Ids
316        - esn: the ElementSetName 'full', 'brief' or 'summary' (default is 'full')
317        - outputschema: the outputSchema (default is 'http://www.opengis.net/cat/csw/2.0.2')
318        - format: the outputFormat (default is 'application/xml')
319
320        """
321
322        # construct request
323        node0 = etree.Element(util.nspath('GetRecordById', namespaces['csw']))
324        node0.set('outputSchema', outputschema)
325        node0.set('outputFormat', format)
326        node0.set('version', self.version)
327        node0.set('service', self.service)
328        node0.set(util.nspath('schemaLocation', namespaces['xsi']), schema_location)
329        for i in id:
330            etree.SubElement(node0, util.nspath('Id', namespaces['csw'])).text = i
331        etree.SubElement(node0, util.nspath('ElementSetName', namespaces['csw'])).text = esn
332        self.request = util.xml2string(etree.tostring(node0))
333
334        # invoke
335        self.response = util.http_post(self.url, self.request, self.lang)
336
337        # parse result
338        self._records = etree.parse(StringIO.StringIO(self.response))
339
340        # check for exceptions
341        self._isexception(self._records, self.owscommon.namespace)
342 
343        if self.exceptionreport is None:
344            self.records = {}
345
346            self._parserecords(outputschema, esn)
347
348    def harvest(self, source, resourcetype, resourceformat=None, harvestinterval=None, responsehandler=None):
349        """
350
351        Construct and process a Harvest request
352
353        Parameters
354        ----------
355
356        - source: a URI to harvest
357        - resourcetype: namespace identifying the type of resource
358        - resourceformat: MIME type of the resource
359        - harvestinterval: frequency of harvesting, in ISO8601
360        - responsehandler: endpoint that CSW should responsd to with response
361
362        """
363
364        # construct request
365        node0 = etree.Element(util.nspath('Harvest', namespaces['csw']))
366        node0.set('version', self.version)
367        node0.set('service', self.service)
368        node0.set(util.nspath('schemaLocation', namespaces['xsi']), schema_location)
369        etree.SubElement(node0, util.nspath('Source', namespaces['csw'])).text = source
370        etree.SubElement(node0, util.nspath('ResourceType', namespaces['csw'])).text = resourcetype
371        if resourceformat is not None:
372            etree.SubElement(node0, util.nspath('ResourceFormat', namespaces['csw'])).text = resourceformat
373        if harvestinterval is not None:
374            etree.SubElement(node0, util.nspath('HarvestInterval', namespaces['csw'])).text = harvestinterval
375        if responsehandler is not None:
376            etree.SubElement(node0, util.nspath('ResponseHandler', namespaces['csw'])).text = responsehandler
377       
378        self.request = util.xml2string(etree.tostring(node0))
379
380        self.response = util.http_post(self.url, self.request, self.lang)
381
382        # parse result
383        self._response = etree.parse(StringIO.StringIO(self.response))
384
385        # check for exceptions
386        self._isexception(self._response, self.owscommon.namespace)
387
388        self.results = {}
389
390        if self.exceptionreport is None:
391            val = self._response.find(util.nspath('Acknowledgement', namespaces['csw']))
392            if util.testXMLValue(val) is not None:
393                ts = val.attrib.get('timeStamp')
394                self.timestamp = util.testXMLValue(ts, True)
395                id = val.find(util.nspath('RequestId', namespaces['csw']))
396                self.id = util.testXMLValue(id) 
397            else:
398                self._parsetransactionsummary()
399
400            self.results['inserted'] = []
401
402            for i in self._response.findall(util.nspath('TransactionResponse/InsertResult', namespaces['csw'])):
403                for j in i.findall(util.nspath('BriefRecord', namespaces['csw']) + '/' + util.nspath('identifier', namespaces['dc'])):
404                    self.results['inserted'].append(util.testXMLValue(j))
405
406    def _parserecords(self, outputschema, esn):
407        if outputschema == namespaces['gmd']: # iso 19139
408            for i in self._records.findall('//'+util.nspath('MD_Metadata', namespaces['gmd'])):
409                val = i.find(util.nspath('fileIdentifier', namespaces['gmd']) + '/' + util.nspath('CharacterString', namespaces['gco']))
410                identifier = self._setidentifierkey(util.testXMLValue(val))
411                self.records[identifier] = MD_Metadata(i)
412        elif outputschema == namespaces['fgdc']: # fgdc csdgm
413            for i in self._records.findall('//metadata'):
414                val = i.find('idinfo/datasetid')
415                identifier = self._setidentifierkey(util.testXMLValue(val))
416                self.records[identifier] = Metadata(i)
417        # CSWs define the dif namespace with the trailing '/' as an
418        # outputSchema, but actual responses indeed define it with '/' as an outputSchema
419        # this is an interoperability issue to be resolved
420        # [:-1] is a workaround for now
421        elif outputschema == namespaces['dif'][:-1]: # nasa dif, strip the trailing '/' for now
422            for i in self._records.findall('//'+util.nspath('DIF', namespaces['dif'])):
423                val = i.find(util.nspath('Entry_ID', namespaces['dif']))
424                identifier = self._setidentifierkey(util.testXMLValue(val))
425                self.records[identifier] = DIF(i)
426        else: # process default
427            for i in self._records.findall('//'+util.nspath(self._setesnel(esn), namespaces['csw'])):
428                val = i.find(util.nspath('identifier', namespaces['dc']))
429                identifier = self._setidentifierkey(util.testXMLValue(val))
430                self.records[identifier] = CswRecord(i)
431
432    def _parsetransactionsummary(self):
433        val = self._response.find(util.nspath('TransactionResponse/TransactionSummary', namespaces['csw']))
434        if val is not None:
435            id = val.attrib.get('requestId')
436            self.results['requestid'] = util.testXMLValue(id, True)
437            ts = val.find(util.nspath('totalInserted', namespaces['csw']))
438            self.results['inserted'] = util.testXMLValue(ts)
439            ts = val.find(util.nspath('totalUpdated', namespaces['csw']))
440            self.results['updated'] = util.testXMLValue(ts)
441            ts = val.find(util.nspath('totalDeleted', namespaces['csw']))
442            self.results['deleted'] = util.testXMLValue(ts)
443
444    def _setesnel(self, esn):
445        """ Set the element name to parse depending on the ElementSetName requested """
446        el = 'Record'
447        if esn == 'brief':
448            el = 'BriefRecord'
449        if esn == 'summary':
450            el = 'SummaryRecord'
451        return el
452
453    def _isexception(self, elem, namespace):
454        val = elem.find(util.nspath('Exception', namespaces['ows']))
455        if val is not None:
456            self.exceptionreport = ExceptionReport(elem, namespace)
457        else:
458            self.exceptionreport = None
459
460    def _setidentifierkey(self, el):
461        if el is None: 
462            return 'owslib_random_%i' % random.randint(1,65536)
463        else:
464            return el
465
466class CswRecord(object):
467    """ Process csw:Record, csw:BriefRecord, csw:SummaryRecord """
468    def __init__(self, record):
469        val = record.find(util.nspath('identifier', namespaces['dc']))
470        self.identifier = util.testXMLValue(val)
471
472        val = record.find(util.nspath('type', namespaces['dc']))
473        self.type = util.testXMLValue(val)
474
475        val = record.find(util.nspath('title', namespaces['dc']))
476        self.title = util.testXMLValue(val)
477
478        val = record.find(util.nspath('abstract', namespaces['dct']))
479        self.abstract = util.testXMLValue(val)
480
481        val = record.find(util.nspath('URI', namespaces['dc']))
482        self.uri = util.testXMLValue(val)
483
484        val = record.find(util.nspath('modified', namespaces['dct']))
485        self.modified = util.testXMLValue(val)
486
487        val = record.find(util.nspath('creator', namespaces['dc']))
488        self.creator = util.testXMLValue(val)
489
490        val = record.find(util.nspath('publisher', namespaces['dc']))
491        self.publisher = util.testXMLValue(val)
492
493        val = record.find(util.nspath('contributor', namespaces['dc']))
494        self.contributor = util.testXMLValue(val)
495
496        val = record.find(util.nspath('language', namespaces['dc']))
497        self.language = util.testXMLValue(val)
498
499        val = record.find(util.nspath('source', namespaces['dc']))
500        self.source = util.testXMLValue(val)
501
502        val = record.find(util.nspath('format', namespaces['dc']))
503        self.format = util.testXMLValue(val)
504
505        self.subjects = []
506        for i in record.findall(util.nspath('subject', namespaces['dc'])):
507            self.subjects.append(util.testXMLValue(i))
508
509        self.rights = []
510        for i in record.findall(util.nspath('rights', namespaces['dc'])):
511            self.rights.append(util.testXMLValue(i))
512
513        val = record.find(util.nspath('BoundingBox', namespaces['ows']))
514        if val is not None:
515            self.bbox = BoundingBox(val, namespaces['ows'])
516        else:
517            self.bbox = None
Note: See TracBrowser for help on using the browser.