root/OWSLib/trunk/owslib/csw.py

Revision 1464, 17.3 KB (checked in by tomkralidis, 5 months ago)

change CSW GetRecords? results to dictionaries

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
14from owslib.etree import etree
15from owslib.filter import *
16from owslib import util
17from owslib.ows import *
18from owslib.iso import *
19
20# default variables
21
22outputformat = 'application/xml'
23schema = 'http://schemas.opengis.net/csw/2.0.2/CSW-discovery.xsd'
24
25namespaces = {
26    None : 'http://www.opengis.net/cat/csw/2.0.2',
27    'csw': 'http://www.opengis.net/cat/csw/2.0.2',
28    'dc' : 'http://purl.org/dc/elements/1.1/',
29    'dct': 'http://purl.org/dc/terms/',
30    'gco': 'http://www.isotc211.org/2005/gco',
31    'gmd': 'http://www.isotc211.org/2005/gmd',
32    'gml': 'http://www.opengis.net/gml',
33    'ogc': 'http://www.opengis.net/ogc',
34    'ows': 'http://www.opengis.net/ows',
35    'rim': 'urn:oasis:names:tc:ebxml-regrep:xsd:rim:3.0',
36    'xs' : 'http://www.w3.org/2001/XMLSchema',
37    'xs2': 'http://www.w3.org/XML/Schema',
38    'xsi': 'http://www.w3.org/2001/XMLSchema-instance'
39}
40
41schema_location = '%s %s' % (namespaces['csw'], schema)
42
43class CatalogueServiceWeb:
44    """ csw request class """
45    def __init__(self, url, lang='en-US', version='2.0.2'):
46        """
47
48        Construct and process a GetCapabilities request
49
50        Parameters
51        ----------
52
53        - url: the URL of the CSW
54        - lang: the language (default is 'en-US')
55        - version: version (default is '2.0.2')
56
57        """
58
59        self.url = url
60        self.lang = lang
61        self.version = version
62        self.service = 'CSW'
63        self.exceptionreport = None
64        self.owscommon = OwsCommon('1.0.0')
65
66        # construct request
67        node0 = etree.Element(util.nspath('GetCapabilities', namespaces['csw']))
68        node0.set('service', self.service)
69        node0.set(util.nspath('schemaLocation', namespaces['xsi']), schema_location)
70        tmp = etree.SubElement(node0, util.nspath('AcceptVersions', namespaces['ows']))
71        etree.SubElement(tmp, util.nspath('Version', namespaces['ows'])).text = self.version
72        tmp2 = etree.SubElement(node0, util.nspath('AcceptFormats', namespaces['ows']))
73        etree.SubElement(tmp2, util.nspath('OutputFormat', namespaces['ows'])).text = outputformat
74        self.request = util.xml2string(etree.tostring(node0))
75
76        # invoke
77        self.response = util.http_post(self.url, self.request, self.lang)
78
79        # parse result
80        self._capabilities = etree.parse(StringIO.StringIO(self.response))
81
82        # check for exceptions
83        self._isexception(self._capabilities, self.owscommon.namespace)
84
85        if self.exceptionreport is None:
86            # ServiceIdentification
87            val = self._capabilities.find(util.nspath('ServiceIdentification', namespaces['ows']))
88            self.identification=ServiceIdentification(val,self.owscommon.namespace)
89            # ServiceProvider
90            val = self._capabilities.find(util.nspath('ServiceProvider', namespaces['ows']))
91            self.provider=ServiceProvider(val,self.owscommon.namespace)
92            # ServiceOperations metadata
93            self.operations=[]
94            for elem in self._capabilities.findall(util.nspath('OperationsMetadata/Operation', namespaces['ows'])):
95                self.operations.append(OperationsMetadata(elem, self.owscommon.namespace))
96   
97            # FilterCapabilities
98            val = self._capabilities.find(util.nspath('Filter_Capabilities', namespaces['ogc']))
99            self.filters=FilterCapabilities(val)
100 
101    def describerecord(self, typename='csw:Record', format=outputformat):
102        """
103
104        Construct and process DescribeRecord request
105
106        Parameters
107        ----------
108
109        - typename: the typename to describe (default is 'csw:Record')
110        - format: the outputFormat (default is 'application/xml')
111 
112        """
113
114        # construct request
115        node0 = etree.Element(util.nspath('DescribeRecord', namespaces['csw']))
116        node0.set('service', self.service)
117        node0.set('version', self.version)
118        node0.set('outputFormat', format)
119        node0.set('schemaLanguage', namespaces['xs2'])
120        node0.set(util.nspath('schemaLocation', namespaces['xsi']), schema_location)
121        etree.SubElement(node0, util.nspath('TypeName', namespaces['csw'])).text = typename
122        self.request = util.xml2string(etree.tostring(node0))
123
124        # invoke
125        self.response = util.http_post(self.url, self.request, self.lang)
126
127        # parse result
128        # TODO: process the XML Schema (you're on your own for now with self.response)
129
130    def getdomain(self, dname, dtype='parameter'):
131        """
132
133        Construct and process a GetDomain request
134
135        Parameters
136        ----------
137
138        - dname: the value of the Parameter or Property to query
139        - dtype: whether to query a parameter (parameter) or property (property)
140
141        """
142
143        # construct request
144        dtypename = 'ParameterName'
145        node0 = etree.Element(util.nspath('GetDomain', namespaces['csw']))
146        node0.set('service', self.service)
147        node0.set('version', self.version)
148        node0.set(util.nspath('schemaLocation', namespaces['xsi']), schema_location)
149        if dtype == 'property':
150            dtypename = 'PropertyName'
151        etree.SubElement(node0, util.nspath(dtypename, namespaces['csw'])).text = dname
152        self.request = util.xml2string(etree.tostring(node0))
153
154        # invoke
155        self.response = util.http_post(self.url, self.request, self.lang)
156
157        # parse result
158        self._values = etree.parse(StringIO.StringIO(self.response))
159
160        # check for exceptions
161        self._isexception(self._values, self.owscommon.namespace)
162
163        if self.exceptionreport is None:
164            self.results = {}
165
166            val = self._values.find(util.nspath('DomainValues', namespaces['csw'])).attrib.get('type')
167            self.results['type'] = util.testXMLValue(val, True)
168
169            val = self._values.find(util.nspath('DomainValues/' + dtypename, namespaces['csw']))
170            self.results[dtype] = util.testXMLValue(val)
171
172            # get the list of values associated with the Domain
173            self.results['values'] = []
174
175            for f in self._values.findall(util.nspath('DomainValues/ListOfValues/Value', namespaces['csw'])):
176                self.results['values'].append(util.testXMLValue(f))
177
178    def getrecords(self, qtype=None, keywords=[], typenames='csw:Record', propertyname='AnyText', bbox=None, esn='full', sortby=None, schema=namespaces['csw'], format=outputformat, startposition=0, maxrecords=10):
179        """
180
181        Construct and process a  GetRecords request
182
183        Parameters
184        ----------
185
186        - qtype: type of resource to query (i.e. service, dataset)
187        - keywords: list of keywords
188        - typenames: the typeNames to query against (default is csw:Record)
189        - propertyname: the PropertyName to Filter against
190        - bbox: the bounding box of the spatial query in the form [minx,miny,maxx,maxy]
191        - esn: the ElementSetName 'full', 'brief' or 'summary' (default is 'full')
192        - sortby: property to sort results on (default is 'dc:title')
193        - schema: the outputSchema (default is 'http://www.opengis.net/cat/csw/2.0.2')
194        - format: the outputFormat (default is 'application/xml')
195        - startposition: requests a slice of the result set, starting at this position (default is 0)
196        - maxrecords: the maximum number of records to return. No records are returned if 0 (default is 10)
197
198        """
199
200        # construct request
201        node0 = etree.Element(util.nspath('GetRecords', namespaces['csw']))
202        node0.set('outputSchema', schema)
203        node0.set('outputFormat', format)
204        node0.set('version', self.version)
205        node0.set('resultType', 'results')
206        node0.set('service', self.service)
207        if startposition > 0:
208            node0.set('startPosition', str(startposition))
209        node0.set('maxRecords', str(maxrecords))
210        node0.set(util.nspath('schemaLocation', namespaces['xsi']), schema_location)
211
212        # decipher number of query parameters ( > 1 sets an 'And' Filter
213        pcount = 0
214        if qtype is not None:
215            pcount += 1
216        if keywords:
217            pcount += 1
218        if bbox is not None:
219            pcount += 1
220
221        node1 = etree.SubElement(node0, util.nspath('Query', namespaces['csw']))
222        node1.set('typeNames', typenames)
223   
224        etree.SubElement(node1, util.nspath('ElementSetName', namespaces['csw'])).text = esn
225
226        # decipher if the query is for real
227        if keywords or bbox is not None or qtype is not None:   
228            node2 = etree.SubElement(node1, util.nspath('Constraint', namespaces['csw']))
229            node2.set('version', '1.1.0')
230            node3 = etree.SubElement(node2, util.nspath('Filter', namespaces['ogc']))
231            node4 = None
232
233            # construct a Filter request
234            flt   = FilterRequest()   
235 
236            if pcount > 1: # Filter should be And-ed
237                node4 = etree.SubElement(node3, util.nspath('And', namespaces['ogc']))
238
239            # set the query type if passed
240            # TODO: need a smarter way to figure these out
241            if qtype is not None:
242                if node4 is not None:
243                    flt.setpropertyisequalto(node4, 'dc:type', qtype)
244                else:
245                    flt.setpropertyisequalto(node3, 'dc:type', qtype)
246
247            # set a bbox query if passed
248            if bbox is not None:
249                if node4 is not None:
250                    flt.setbbox(node4, bbox)
251                else:
252                    flt.setbbox(node3, bbox)
253
254            # set a keyword query if passed
255            if len(keywords) > 0:
256                if len(keywords) > 1: # loop multiple keywords into an Or
257                    if node4 is not None:
258                        node5 = etree.SubElement(node4, util.nspath('Or', namespaces['ogc']))
259                    else:
260                        node5 = etree.SubElement(node3, util.nspath('Or', namespaces['ogc']))
261
262                    for i in keywords:
263                        flt.setpropertyislike(node5, propertyname, '%%%s%%' % i)
264
265                else: # one keyword
266                    if node4 is not None:
267                        flt.setpropertyislike(node4, propertyname, '%%%s%%' % keywords[0])
268                    else:
269                        flt.setpropertyislike(node3, propertyname, '%%%s%%' % keywords[0])
270
271        # set a sort if passed
272        if sortby is not None:
273            flt.setsortby(node1, sortby)
274   
275        self.request = util.xml2string(etree.tostring(node0))
276
277        # invoke
278        self.response = util.http_post(self.url, self.request, self.lang)
279
280        # parse result
281        self._records = etree.parse(StringIO.StringIO(self.response))
282
283        # check for exceptions
284        self._isexception(self._capabilities, self.owscommon.namespace)
285 
286        if self.exceptionreport is None:
287            self.results = {}
288   
289            # process search results attributes
290            val = self._records.find(util.nspath('SearchResults', namespaces['csw'])).attrib.get('numberOfRecordsMatched')
291            self.results['matches'] = int(util.testXMLValue(val, True))
292            val = self._records.find(util.nspath('SearchResults', namespaces['csw'])).attrib.get('numberOfRecordsReturned')
293            self.results['returned'] = int(util.testXMLValue(val, True))
294            val = self._records.find(util.nspath('SearchResults', namespaces['csw'])).attrib.get('nextRecord')
295            self.results['nextrecord'] = int(util.testXMLValue(val, True))
296   
297            # process list of matching records
298            self.records = {}
299
300            if schema == 'http://www.isotc211.org/2005/gmd': # iso 19139
301                for f in self._records.findall(util.nspath('SearchResults', namespaces['csw']) + '/' + util.nspath('MD_Metadata', namespaces['gmd'])):
302                    val = f.find(util.nspath('fileIdentifier', namespaces['gmd']) + '/' + util.nspath('CharacterString', namespaces['gco']))
303                    identifier = util.testXMLValue(val)
304                    self.records[identifier] = MD_Metadata(f)
305            else: # process default
306                for f in self._records.findall(util.nspath('SearchResults/' + self._setesnel(esn), namespaces['csw'])):
307                    val = f.find(util.nspath('identifier', namespaces['dc']))
308                    identifier = util.testXMLValue(val)
309                    self.records[identifier] = CswRecord(f)
310
311    def getrecordbyid(self, id=[], esn='full', schema=namespaces['csw'], format=outputformat):
312        """
313
314        Construct and process a GetRecordById request
315
316        Parameters
317        ----------
318
319        - id: the list of Ids
320        - esn: the ElementSetName 'full', 'brief' or 'summary' (default is 'full')
321        - schema: the outputSchema (default is 'http://www.opengis.net/cat/csw/2.0.2')
322        - format: the outputFormat (default is 'application/xml')
323
324        """
325
326        # construct request
327        node0 = etree.Element(util.nspath('GetRecordById', namespaces['csw']))
328        node0.set('outputSchema', schema)
329        node0.set('outputFormat', format)
330        node0.set('version', self.version)
331        node0.set('service', self.service)
332        node0.set(util.nspath('schemaLocation', namespaces['xsi']), schema_location)
333        for i in id:
334            etree.SubElement(node0, util.nspath('Id', namespaces['csw'])).text = i
335        etree.SubElement(node0, util.nspath('ElementSetName', namespaces['csw'])).text = esn
336        self.request = util.xml2string(etree.tostring(node0))
337
338        # invoke
339        self.response = util.http_post(self.url, self.request, self.lang)
340
341        # parse result
342        self._records = etree.parse(StringIO.StringIO(self.response))
343
344        # check for exceptions
345        self._isexception(self._capabilities, self.owscommon.namespace)
346 
347        if self.exceptionreport is None:
348            self.records = {}
349
350            # process matching record
351            if schema == 'http://www.isotc211.org/2005/gmd': # iso 19139
352                for i in self._records.findall(util.nspath('MD_Metadata', namespaces['gmd'])):
353                    val = i.find(util.nspath('fileIdentifier', namespaces['gmd']) + '/' + util.nspath('CharacterString', namespaces['gco']))
354                    identifier = util.testXMLValue(val)
355                    self.records[identifier] = MD_Metadata(i)
356            else: # process default
357                for i in self._records.findall(util.nspath(self._setesnel(esn), namespaces['csw'])):
358                    val = i.find(util.nspath('identifier', namespaces['dc']))
359                    identifier = util.testXMLValue(val)
360                    self.records[identifier] = CswRecord(i)
361
362    def _setesnel(self, esn):
363        """ Set the element name to parse depending on the ElementSetName requested """
364        el = 'Record'
365        if esn == 'brief':
366            el = 'BriefRecord'
367        if esn == 'summary':
368            el = 'SummaryRecord'
369        return el
370
371    def _isexception(self, elem, namespace):
372        val = elem.find(util.nspath('Exception', namespaces['ows']))
373        if val is not None:
374            self.exceptionreport = ExceptionReport(elem, namespace)
375        else:
376            self.exceptionreport = None
377
378class CswRecord(object):
379    """ Process csw:Record, csw:BriefRecord, csw:SummaryRecord """
380    def __init__(self, record):
381        val = record.find(util.nspath('identifier', namespaces['dc']))
382        self.identifier = util.testXMLValue(val)
383
384        val = record.find(util.nspath('type', namespaces['dc']))
385        self.type = util.testXMLValue(val)
386
387        val = record.find(util.nspath('title', namespaces['dc']))
388        self.title = util.testXMLValue(val)
389
390        val = record.find(util.nspath('abstract', namespaces['dct']))
391        self.abstract = util.testXMLValue(val)
392
393        val = record.find(util.nspath('URI', namespaces['dc']))
394        self.uri = util.testXMLValue(val)
395
396        val = record.find(util.nspath('modified', namespaces['dct']))
397        self.modified = util.testXMLValue(val)
398
399        val = record.find(util.nspath('creator', namespaces['dc']))
400        self.creator = util.testXMLValue(val)
401
402        val = record.find(util.nspath('publisher', namespaces['dc']))
403        self.publisher = util.testXMLValue(val)
404
405        val = record.find(util.nspath('contributor', namespaces['dc']))
406        self.contributor = util.testXMLValue(val)
407
408        val = record.find(util.nspath('language', namespaces['dc']))
409        self.language = util.testXMLValue(val)
410
411        val = record.find(util.nspath('source', namespaces['dc']))
412        self.source = util.testXMLValue(val)
413
414        val = record.find(util.nspath('format', namespaces['dc']))
415        self.format = util.testXMLValue(val)
416
417        self.subjects = []
418        for i in record.findall(util.nspath('subject', namespaces['dc'])):
419            self.subjects.append(util.testXMLValue(i))
420
421        self.rights = []
422        for i in record.findall(util.nspath('rights', namespaces['dc'])):
423            self.rights.append(util.testXMLValue(i))
424
425        val = record.find(util.nspath('BoundingBox', namespaces['ows']))
426        if val is not None:
427            self.bbox = BoundingBox(val, namespaces['ows'])
428        else:
429            self.bbox = None
Note: See TracBrowser for help on using the browser.