# -*- coding: ISO-8859-15 -*- # ============================================================================= # Copyright (c) 2009 Tom Kralidis # # Authors : Tom Kralidis # # Contact email: tomkralidis@gmail.com # ============================================================================= """ CSW request and response processor """ from __future__ import (absolute_import, division, print_function) import inspect import warnings import six try: from StringIO import StringIO as BytesIO # Python 2 except ImportError: from io import BytesIO # Python 3 import random try: # Python 3 from urllib.parse import urlencode except ImportError: # Python 2 from urllib import urlencode from owslib.util import OrderedDict from owslib.etree import etree from owslib import fes from owslib import util from owslib import ows from owslib.iso import MD_Metadata from owslib.fgdc import Metadata from owslib.dif import DIF from owslib.gm03 import GM03 from owslib.namespaces import Namespaces from owslib.util import cleanup_namespaces, bind_url, add_namespaces, openURL # default variables outputformat = 'application/xml' def get_namespaces(): n = Namespaces() return n.get_namespaces() namespaces = get_namespaces() schema = 'http://schemas.opengis.net/csw/2.0.2/CSW-discovery.xsd' schema_location = '%s %s' % (namespaces['csw'], schema) class CatalogueServiceWeb(object): """ csw request class """ def __init__(self, url, lang='en-US', version='2.0.2', timeout=10, skip_caps=False, username=None, password=None): """ Construct and process a GetCapabilities request Parameters ---------- - url: the URL of the CSW - lang: the language (default is 'en-US') - version: version (default is '2.0.2') - timeout: timeout in seconds - skip_caps: whether to skip GetCapabilities processing on init (default is False) - username: username for HTTP basic authentication - password: password for HTTP basic authentication """ self.url = url self.lang = lang self.version = version self.timeout = timeout self.username = username self.password = password self.service = 'CSW' self.exceptionreport = None self.owscommon = ows.OwsCommon('1.0.0') if not skip_caps: # process GetCapabilities # construct request data = {'service': self.service, 'version': self.version, 'request': 'GetCapabilities'} self.request = urlencode(data) self._invoke() if self.exceptionreport is None: # ServiceIdentification val = self._exml.find(util.nspath_eval('ows:ServiceIdentification', namespaces)) if val is not None: self.identification = ows.ServiceIdentification(val,self.owscommon.namespace) else: self.identification = None # ServiceProvider val = self._exml.find(util.nspath_eval('ows:ServiceProvider', namespaces)) if val is not None: self.provider = ows.ServiceProvider(val,self.owscommon.namespace) else: self.provider = None # ServiceOperations metadata self.operations = [] for elem in self._exml.findall(util.nspath_eval('ows:OperationsMetadata/ows:Operation', namespaces)): self.operations.append(ows.OperationsMetadata(elem, self.owscommon.namespace)) # FilterCapabilities val = self._exml.find(util.nspath_eval('ogc:Filter_Capabilities', namespaces)) self.filters = fes.FilterCapabilities(val) def describerecord(self, typename='csw:Record', format=outputformat): """ Construct and process DescribeRecord request Parameters ---------- - typename: the typename to describe (default is 'csw:Record') - format: the outputFormat (default is 'application/xml') """ # construct request node0 = self._setrootelement('csw:DescribeRecord') node0.set('service', self.service) node0.set('version', self.version) node0.set('outputFormat', format) node0.set('schemaLanguage', namespaces['xs2']) node0.set(util.nspath_eval('xsi:schemaLocation', namespaces), schema_location) etree.SubElement(node0, util.nspath_eval('csw:TypeName', namespaces)).text = typename self.request = node0 self._invoke() # parse result # TODO: process the XML Schema (you're on your own for now with self.response) def getdomain(self, dname, dtype='parameter'): """ Construct and process a GetDomain request Parameters ---------- - dname: the value of the Parameter or Property to query - dtype: whether to query a parameter (parameter) or property (property) """ # construct request dtypename = 'ParameterName' node0 = self._setrootelement('csw:GetDomain') node0.set('service', self.service) node0.set('version', self.version) node0.set(util.nspath_eval('xsi:schemaLocation', namespaces), schema_location) if dtype == 'property': dtypename = 'PropertyName' etree.SubElement(node0, util.nspath_eval('csw:%s' % dtypename, namespaces)).text = dname self.request = node0 self._invoke() if self.exceptionreport is None: self.results = {} val = self._exml.find(util.nspath_eval('csw:DomainValues', namespaces)).attrib.get('type') self.results['type'] = util.testXMLValue(val, True) val = self._exml.find(util.nspath_eval('csw:DomainValues/csw:%s' % dtypename, namespaces)) self.results[dtype] = util.testXMLValue(val) # get the list of values associated with the Domain self.results['values'] = [] for f in self._exml.findall(util.nspath_eval('csw:DomainValues/csw:ListOfValues/csw:Value', namespaces)): self.results['values'].append(util.testXMLValue(f)) def getrecords(self, qtype=None, keywords=[], typenames='csw:Record', propertyname='csw:AnyText', bbox=None, esn='summary', sortby=None, outputschema=namespaces['csw'], format=outputformat, startposition=0, maxrecords=10, cql=None, xml=None, resulttype='results'): """ Construct and process a GetRecords request Parameters ---------- - qtype: type of resource to query (i.e. service, dataset) - keywords: list of keywords - typenames: the typeNames to query against (default is csw:Record) - propertyname: the PropertyName to Filter against - bbox: the bounding box of the spatial query in the form [minx,miny,maxx,maxy] - esn: the ElementSetName 'full', 'brief' or 'summary' (default is 'summary') - sortby: property to sort results on - outputschema: the outputSchema (default is 'http://www.opengis.net/cat/csw/2.0.2') - format: the outputFormat (default is 'application/xml') - startposition: requests a slice of the result set, starting at this position (default is 0) - maxrecords: the maximum number of records to return. No records are returned if 0 (default is 10) - cql: common query language text. Note this overrides bbox, qtype, keywords - xml: raw XML request. Note this overrides all other options - resulttype: the resultType 'hits', 'results', 'validate' (default is 'results') """ warnings.warn("""Please use the updated 'getrecords2' method instead of 'getrecords'. The 'getrecords' method will be upgraded to use the 'getrecords2' parameters in a future version of OWSLib.""") if xml is not None: self.request = etree.fromstring(xml) val = self.request.find(util.nspath_eval('csw:Query/csw:ElementSetName', namespaces)) if val is not None: esn = util.testXMLValue(val) else: # construct request node0 = self._setrootelement('csw:GetRecords') if etree.__name__ != 'lxml.etree': # apply nsmap manually node0.set('xmlns:ows', namespaces['ows']) node0.set('xmlns:gmd', namespaces['gmd']) node0.set('xmlns:dif', namespaces['dif']) node0.set('xmlns:fgdc', namespaces['fgdc']) node0.set('outputSchema', outputschema) node0.set('outputFormat', format) node0.set('version', self.version) node0.set('resultType', resulttype) node0.set('service', self.service) if startposition > 0: node0.set('startPosition', str(startposition)) node0.set('maxRecords', str(maxrecords)) node0.set(util.nspath_eval('xsi:schemaLocation', namespaces), schema_location) node1 = etree.SubElement(node0, util.nspath_eval('csw:Query', namespaces)) node1.set('typeNames', typenames) etree.SubElement(node1, util.nspath_eval('csw:ElementSetName', namespaces)).text = esn self._setconstraint(node1, qtype, propertyname, keywords, bbox, cql, None) if sortby is not None: fes.setsortby(node1, sortby) self.request = node0 self._invoke() if self.exceptionreport is None: self.results = {} # process search results attributes val = self._exml.find(util.nspath_eval('csw:SearchResults', namespaces)).attrib.get('numberOfRecordsMatched') self.results['matches'] = int(util.testXMLValue(val, True)) val = self._exml.find(util.nspath_eval('csw:SearchResults', namespaces)).attrib.get('numberOfRecordsReturned') self.results['returned'] = int(util.testXMLValue(val, True)) val = self._exml.find(util.nspath_eval('csw:SearchResults', namespaces)).attrib.get('nextRecord') self.results['nextrecord'] = int(util.testXMLValue(val, True)) # process list of matching records self.records = OrderedDict() self._parserecords(outputschema, esn) def getrecordbyid(self, id=[], esn='full', outputschema=namespaces['csw'], format=outputformat): """ Construct and process a GetRecordById request Parameters ---------- - id: the list of Ids - esn: the ElementSetName 'full', 'brief' or 'summary' (default is 'full') - outputschema: the outputSchema (default is 'http://www.opengis.net/cat/csw/2.0.2') - format: the outputFormat (default is 'application/xml') """ # construct request data = { 'service': self.service, 'version': self.version, 'request': 'GetRecordById', 'outputFormat': format, 'outputSchema': outputschema, 'elementsetname': esn, 'id': ','.join(id), } self.request = urlencode(data) self._invoke() if self.exceptionreport is None: self.results = {} self.records = OrderedDict() self._parserecords(outputschema, esn) def getrecords2(self, constraints=[], sortby=None, typenames='csw:Record', esn='summary', outputschema=namespaces['csw'], format=outputformat, startposition=0, maxrecords=10, cql=None, xml=None, resulttype='results'): """ Construct and process a GetRecords request Parameters ---------- - constraints: the list of constraints (OgcExpression from owslib.fes module) - sortby: an OGC SortBy object (SortBy from owslib.fes module) - typenames: the typeNames to query against (default is csw:Record) - esn: the ElementSetName 'full', 'brief' or 'summary' (default is 'summary') - outputschema: the outputSchema (default is 'http://www.opengis.net/cat/csw/2.0.2') - format: the outputFormat (default is 'application/xml') - startposition: requests a slice of the result set, starting at this position (default is 0) - maxrecords: the maximum number of records to return. No records are returned if 0 (default is 10) - cql: common query language text. Note this overrides bbox, qtype, keywords - xml: raw XML request. Note this overrides all other options - resulttype: the resultType 'hits', 'results', 'validate' (default is 'results') """ if xml is not None: self.request = etree.fromstring(xml) val = self.request.find(util.nspath_eval('csw:Query/csw:ElementSetName', namespaces)) if val is not None: esn = util.testXMLValue(val) val = self.request.attrib.get('outputSchema') if val is not None: outputschema = util.testXMLValue(val, True) else: # construct request node0 = self._setrootelement('csw:GetRecords') if etree.__name__ != 'lxml.etree': # apply nsmap manually node0.set('xmlns:ows', namespaces['ows']) node0.set('xmlns:gmd', namespaces['gmd']) node0.set('xmlns:dif', namespaces['dif']) node0.set('xmlns:fgdc', namespaces['fgdc']) node0.set('outputSchema', outputschema) node0.set('outputFormat', format) node0.set('version', self.version) node0.set('service', self.service) node0.set('resultType', resulttype) if startposition > 0: node0.set('startPosition', str(startposition)) node0.set('maxRecords', str(maxrecords)) node0.set(util.nspath_eval('xsi:schemaLocation', namespaces), schema_location) node1 = etree.SubElement(node0, util.nspath_eval('csw:Query', namespaces)) node1.set('typeNames', typenames) etree.SubElement(node1, util.nspath_eval('csw:ElementSetName', namespaces)).text = esn if any([len(constraints) > 0, cql is not None]): node2 = etree.SubElement(node1, util.nspath_eval('csw:Constraint', namespaces)) node2.set('version', '1.1.0') flt = fes.FilterRequest() if len(constraints) > 0: node2.append(flt.setConstraintList(constraints)) # Now add a CQL filter if passed in elif cql is not None: etree.SubElement(node2, util.nspath_eval('csw:CqlText', namespaces)).text = cql if sortby is not None and isinstance(sortby, fes.SortBy): node1.append(sortby.toXML()) self.request = node0 self._invoke() if self.exceptionreport is None: self.results = {} # process search results attributes val = self._exml.find(util.nspath_eval('csw:SearchResults', namespaces)).attrib.get('numberOfRecordsMatched') self.results['matches'] = int(util.testXMLValue(val, True)) val = self._exml.find(util.nspath_eval('csw:SearchResults', namespaces)).attrib.get('numberOfRecordsReturned') self.results['returned'] = int(util.testXMLValue(val, True)) val = self._exml.find(util.nspath_eval('csw:SearchResults', namespaces)).attrib.get('nextRecord') if val is not None: self.results['nextrecord'] = int(util.testXMLValue(val, True)) else: warnings.warn("""CSW Server did not supply a nextRecord value (it is optional), so the client should page through the results in another way.""") # For more info, see: # https://github.com/geopython/OWSLib/issues/100 self.results['nextrecord'] = None # process list of matching records self.records = OrderedDict() self._parserecords(outputschema, esn) def transaction(self, ttype=None, typename='csw:Record', record=None, propertyname=None, propertyvalue=None, bbox=None, keywords=[], cql=None, identifier=None): """ Construct and process a Transaction request Parameters ---------- - ttype: the type of transaction 'insert, 'update', 'delete' - typename: the typename to describe (default is 'csw:Record') - record: the XML record to insert - propertyname: the RecordProperty/PropertyName to Filter against - propertyvalue: the RecordProperty Value to Filter against (for updates) - bbox: the bounding box of the spatial query in the form [minx,miny,maxx,maxy] - keywords: list of keywords - cql: common query language text. Note this overrides bbox, qtype, keywords - identifier: record identifier. Note this overrides bbox, qtype, keywords, cql """ # construct request node0 = self._setrootelement('csw:Transaction') node0.set('version', self.version) node0.set('service', self.service) node0.set(util.nspath_eval('xsi:schemaLocation', namespaces), schema_location) validtransactions = ['insert', 'update', 'delete'] if ttype not in validtransactions: # invalid transaction raise RuntimeError('Invalid transaction \'%s\'.' % ttype) node1 = etree.SubElement(node0, util.nspath_eval('csw:%s' % ttype.capitalize(), namespaces)) if ttype != 'update': node1.set('typeName', typename) if ttype == 'insert': if record is None: raise RuntimeError('Nothing to insert.') node1.append(etree.fromstring(record)) if ttype == 'update': if record is not None: node1.append(etree.fromstring(record)) else: if propertyname is not None and propertyvalue is not None: node2 = etree.SubElement(node1, util.nspath_eval('csw:RecordProperty', namespaces)) etree.SubElement(node2, util.nspath_eval('csw:Name', namespaces)).text = propertyname etree.SubElement(node2, util.nspath_eval('csw:Value', namespaces)).text = propertyvalue self._setconstraint(node1, qtype, propertyname, keywords, bbox, cql, identifier) if ttype == 'delete': self._setconstraint(node1, None, propertyname, keywords, bbox, cql, identifier) self.request = node0 self._invoke() self.results = {} if self.exceptionreport is None: self._parsetransactionsummary() self._parseinsertresult() def harvest(self, source, resourcetype, resourceformat=None, harvestinterval=None, responsehandler=None): """ Construct and process a Harvest request Parameters ---------- - source: a URI to harvest - resourcetype: namespace identifying the type of resource - resourceformat: MIME type of the resource - harvestinterval: frequency of harvesting, in ISO8601 - responsehandler: endpoint that CSW should responsd to with response """ # construct request node0 = self._setrootelement('csw:Harvest') node0.set('version', self.version) node0.set('service', self.service) node0.set(util.nspath_eval('xsi:schemaLocation', namespaces), schema_location) etree.SubElement(node0, util.nspath_eval('csw:Source', namespaces)).text = source etree.SubElement(node0, util.nspath_eval('csw:ResourceType', namespaces)).text = resourcetype if resourceformat is not None: etree.SubElement(node0, util.nspath_eval('csw:ResourceFormat', namespaces)).text = resourceformat if harvestinterval is not None: etree.SubElement(node0, util.nspath_eval('csw:HarvestInterval', namespaces)).text = harvestinterval if responsehandler is not None: etree.SubElement(node0, util.nspath_eval('csw:ResponseHandler', namespaces)).text = responsehandler self.request = node0 self._invoke() self.results = {} if self.exceptionreport is None: val = self._exml.find(util.nspath_eval('csw:Acknowledgement', namespaces)) if util.testXMLValue(val) is not None: ts = val.attrib.get('timeStamp') self.timestamp = util.testXMLValue(ts, True) id = val.find(util.nspath_eval('csw:RequestId', namespaces)) self.id = util.testXMLValue(id) else: self._parsetransactionsummary() self._parseinsertresult() def get_operation_by_name(self, name): """Return a named operation""" for item in self.operations: if item.name.lower() == name.lower(): return item raise KeyError("No operation named %s" % name) def getService_urls(self, service_string=None): """ Return easily identifiable URLs for all service types Parameters ---------- - service_string: a URI to lookup """ urls=[] for key,rec in six.iteritems(self.records): #create a generator object, and iterate through it until the match is found #if not found, gets the default value (here "none") url = next((d['url'] for d in rec.references if d['scheme'] == service_string), None) if url is not None: urls.append(url) return urls def _parseinsertresult(self): self.results['insertresults'] = [] for i in self._exml.findall('.//'+util.nspath_eval('csw:InsertResult', namespaces)): for j in i.findall(util.nspath_eval('csw:BriefRecord/dc:identifier', namespaces)): self.results['insertresults'].append(util.testXMLValue(j)) def _parserecords(self, outputschema, esn): if outputschema == namespaces['gmd']: # iso 19139 for i in self._exml.findall('.//'+util.nspath_eval('gmd:MD_Metadata', namespaces)) or self._exml.findall('.//'+util.nspath_eval('gmi:MI_Metadata', namespaces)): val = i.find(util.nspath_eval('gmd:fileIdentifier/gco:CharacterString', namespaces)) identifier = self._setidentifierkey(util.testXMLValue(val)) self.records[identifier] = MD_Metadata(i) elif outputschema == namespaces['fgdc']: # fgdc csdgm for i in self._exml.findall('.//metadata'): val = i.find('idinfo/datasetid') identifier = self._setidentifierkey(util.testXMLValue(val)) self.records[identifier] = Metadata(i) elif outputschema == namespaces['dif']: # nasa dif for i in self._exml.findall('.//'+util.nspath_eval('dif:DIF', namespaces)): val = i.find(util.nspath_eval('dif:Entry_ID', namespaces)) identifier = self._setidentifierkey(util.testXMLValue(val)) self.records[identifier] = DIF(i) elif outputschema == namespaces['gm03']: # GM03 for i in self._exml.findall('.//'+util.nspath_eval('gm03:TRANSFER', namespaces)): val = i.find(util.nspath_eval('gm03:fileIdentifier', namespaces)) identifier = self._setidentifierkey(util.testXMLValue(val)) self.records[identifier] = GM03(i) else: # process default for i in self._exml.findall('.//'+util.nspath_eval('csw:%s' % self._setesnel(esn), namespaces)): val = i.find(util.nspath_eval('dc:identifier', namespaces)) identifier = self._setidentifierkey(util.testXMLValue(val)) self.records[identifier] = CswRecord(i) def _parsetransactionsummary(self): val = self._exml.find(util.nspath_eval('csw:TransactionResponse/csw:TransactionSummary', namespaces)) if val is not None: rid = val.attrib.get('requestId') self.results['requestid'] = util.testXMLValue(rid, True) ts = val.find(util.nspath_eval('csw:totalInserted', namespaces)) self.results['inserted'] = int(util.testXMLValue(ts)) ts = val.find(util.nspath_eval('csw:totalUpdated', namespaces)) self.results['updated'] = int(util.testXMLValue(ts)) ts = val.find(util.nspath_eval('csw:totalDeleted', namespaces)) self.results['deleted'] = int(util.testXMLValue(ts)) def _setesnel(self, esn): """ Set the element name to parse depending on the ElementSetName requested """ el = 'Record' if esn == 'brief': el = 'BriefRecord' if esn == 'summary': el = 'SummaryRecord' return el def _setidentifierkey(self, el): if el is None: return 'owslib_random_%i' % random.randint(1,65536) else: return el def _setrootelement(self, el): if etree.__name__ == 'lxml.etree': # apply nsmap return etree.Element(util.nspath_eval(el, namespaces), nsmap=namespaces) else: return etree.Element(util.nspath_eval(el, namespaces)) def _setconstraint(self, parent, qtype=None, propertyname='csw:AnyText', keywords=[], bbox=None, cql=None, identifier=None): if keywords or bbox is not None or qtype is not None or cql is not None or identifier is not None: node0 = etree.SubElement(parent, util.nspath_eval('csw:Constraint', namespaces)) node0.set('version', '1.1.0') if identifier is not None: # set identifier filter, overrides all other parameters flt = fes.FilterRequest() node0.append(flt.set(identifier=identifier)) elif cql is not None: # send raw CQL query # CQL passed, overrides all other parameters node1 = etree.SubElement(node0, util.nspath_eval('csw:CqlText', namespaces)) node1.text = cql else: # construct a Filter request flt = fes.FilterRequest() node0.append(flt.set(qtype=qtype, keywords=keywords, propertyname=propertyname,bbox=bbox)) def _invoke(self): # do HTTP request request_url = self.url # Get correct URL based on Operation list. # If skip_caps=True, then self.operations has not been set, so use # default URL. if hasattr(self, 'operations'): caller = inspect.stack()[1][3] if caller == 'getrecords2': caller = 'getrecords' try: op = self.get_operation_by_name(caller) if isinstance(self.request, six.string_types): # GET KVP get_verbs = [x for x in op.methods if x.get('type').lower() == 'get'] request_url = get_verbs[0].get('url') else: post_verbs = [x for x in op.methods if x.get('type').lower() == 'post'] if len(post_verbs) > 1: # Filter by constraints. We must match a PostEncoding of "XML" for pv in post_verbs: for const in pv.get('constraints'): if const.name.lower() == 'postencoding': values = [v.lower() for v in const.values] if 'xml' in values: request_url = pv.get('url') break else: # Well, just use the first one. request_url = post_verbs[0].get('url') elif len(post_verbs) == 1: request_post_url = post_verbs[0].get('url') except: # no such luck, just go with request_url pass if isinstance(self.request, six.string_types): # GET KVP self.request = '%s%s' % (bind_url(request_url), self.request) self.response = openURL(self.request, None, 'Get', username=self.username, password=self.password, timeout=self.timeout).read() else: self.request = cleanup_namespaces(self.request) # Add any namespaces used in the "typeNames" attribute of the # csw:Query element to the query's xml namespaces. for query in self.request.findall(util.nspath_eval('csw:Query', namespaces)): ns = query.get("typeNames", None) if ns is not None: # Pull out "gmd" from something like "gmd:MD_Metadata" from the list # of typenames ns_keys = [x.split(':')[0] for x in ns.split(' ')] self.request = add_namespaces(self.request, ns_keys) self.request = util.element_to_string(self.request, encoding='utf-8') self.response = util.http_post(request_url, self.request, self.lang, self.timeout, self.username, self.password) # parse result see if it's XML self._exml = etree.parse(BytesIO(self.response)) # it's XML. Attempt to decipher whether the XML response is CSW-ish """ valid_xpaths = [ util.nspath_eval('ows:ExceptionReport', namespaces), util.nspath_eval('csw:Capabilities', namespaces), util.nspath_eval('csw:DescribeRecordResponse', namespaces), util.nspath_eval('csw:GetDomainResponse', namespaces), util.nspath_eval('csw:GetRecordsResponse', namespaces), util.nspath_eval('csw:GetRecordByIdResponse', namespaces), util.nspath_eval('csw:HarvestResponse', namespaces), util.nspath_eval('csw:TransactionResponse', namespaces) ] if self._exml.getroot().tag not in valid_xpaths: raise RuntimeError('Document is XML, but not CSW-ish') # check if it's an OGC Exception val = self._exml.find(util.nspath_eval('ows:Exception', namespaces)) if val is not None: raise ows.ExceptionReport(self._exml, self.owscommon.namespace) else: self.exceptionreport = None class CswRecord(object): """ Process csw:Record, csw:BriefRecord, csw:SummaryRecord """ def __init__(self, record): if hasattr(record, 'getroot'): # standalone document self.xml = etree.tostring(record.getroot()) else: # part of a larger document self.xml = etree.tostring(record) # check to see if Dublin Core record comes from # rdf:RDF/rdf:Description container # (child content model is identical) self.rdf = False rdf = record.find(util.nspath_eval('rdf:Description', namespaces)) if rdf is not None: self.rdf = True record = rdf # some CSWs return records with multiple identifiers based on # different schemes. Use the first dc:identifier value to set # self.identifier, and set self.identifiers as a list of dicts val = record.find(util.nspath_eval('dc:identifier', namespaces)) self.identifier = util.testXMLValue(val) self.identifiers = [] for i in record.findall(util.nspath_eval('dc:identifier', namespaces)): d = {} d['scheme'] = i.attrib.get('scheme') d['identifier'] = i.text self.identifiers.append(d) val = record.find(util.nspath_eval('dc:type', namespaces)) self.type = util.testXMLValue(val) val = record.find(util.nspath_eval('dc:title', namespaces)) self.title = util.testXMLValue(val) val = record.find(util.nspath_eval('dct:alternative', namespaces)) self.alternative = util.testXMLValue(val) val = record.find(util.nspath_eval('dct:isPartOf', namespaces)) self.ispartof = util.testXMLValue(val) val = record.find(util.nspath_eval('dct:abstract', namespaces)) self.abstract = util.testXMLValue(val) val = record.find(util.nspath_eval('dc:date', namespaces)) self.date = util.testXMLValue(val) val = record.find(util.nspath_eval('dct:created', namespaces)) self.created = util.testXMLValue(val) val = record.find(util.nspath_eval('dct:issued', namespaces)) self.issued = util.testXMLValue(val) val = record.find(util.nspath_eval('dc:relation', namespaces)) self.relation = util.testXMLValue(val) val = record.find(util.nspath_eval('dct:temporal', namespaces)) self.temporal = util.testXMLValue(val) self.uris = [] # list of dicts for i in record.findall(util.nspath_eval('dc:URI', namespaces)): uri = {} uri['protocol'] = util.testXMLValue(i.attrib.get('protocol'), True) uri['name'] = util.testXMLValue(i.attrib.get('name'), True) uri['description'] = util.testXMLValue(i.attrib.get('description'), True) uri['url'] = util.testXMLValue(i) self.uris.append(uri) self.references = [] # list of dicts for i in record.findall(util.nspath_eval('dct:references', namespaces)): ref = {} ref['scheme'] = util.testXMLValue(i.attrib.get('scheme'), True) ref['url'] = util.testXMLValue(i) self.references.append(ref) val = record.find(util.nspath_eval('dct:modified', namespaces)) self.modified = util.testXMLValue(val) val = record.find(util.nspath_eval('dc:creator', namespaces)) self.creator = util.testXMLValue(val) val = record.find(util.nspath_eval('dc:publisher', namespaces)) self.publisher = util.testXMLValue(val) val = record.find(util.nspath_eval('dc:coverage', namespaces)) self.coverage = util.testXMLValue(val) val = record.find(util.nspath_eval('dc:contributor', namespaces)) self.contributor = util.testXMLValue(val) val = record.find(util.nspath_eval('dc:language', namespaces)) self.language = util.testXMLValue(val) val = record.find(util.nspath_eval('dc:source', namespaces)) self.source = util.testXMLValue(val) val = record.find(util.nspath_eval('dct:rightsHolder', namespaces)) self.rightsholder = util.testXMLValue(val) val = record.find(util.nspath_eval('dct:accessRights', namespaces)) self.accessrights = util.testXMLValue(val) val = record.find(util.nspath_eval('dct:license', namespaces)) self.license = util.testXMLValue(val) val = record.find(util.nspath_eval('dc:format', namespaces)) self.format = util.testXMLValue(val) self.subjects = [] for i in record.findall(util.nspath_eval('dc:subject', namespaces)): self.subjects.append(util.testXMLValue(i)) self.rights = [] for i in record.findall(util.nspath_eval('dc:rights', namespaces)): self.rights.append(util.testXMLValue(i)) val = record.find(util.nspath_eval('dct:spatial', namespaces)) self.spatial = util.testXMLValue(val) val = record.find(util.nspath_eval('ows:BoundingBox', namespaces)) if val is not None: self.bbox = ows.BoundingBox(val, namespaces['ows']) else: self.bbox = None val = record.find(util.nspath_eval('ows:WGS84BoundingBox', namespaces)) if val is not None: self.bbox_wgs84 = ows.WGS84BoundingBox(val, namespaces['ows']) else: self.bbox_wgs84 = None