# -*- coding: ISO-8859-15 -*- # ============================================================================= # Copyright (c) 2009 Tom Kralidis # # Authors : Tom Kralidis # # Contact email: tomkralidis@gmail.com # ============================================================================= """ CSW request and response processor """ import inspect import warnings import StringIO import random from urllib import urlencode from urllib2 import urlopen 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.namespaces import Namespaces from owslib.util import cleanup_namespaces, bind_url, add_namespaces # 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: """ csw request class """ def __init__(self, url, lang='en-US', version='2.0.2', timeout=10, skip_caps=False): """ 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) """ self.url = url self.lang = lang self.version = version self.timeout = timeout 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 = '%s%s' % (bind_url(self.url), urlencode(data)) self._invoke() if self.exceptionreport is None: # ServiceIdentification val = self._exml.find(util.nspath_eval('ows:ServiceIdentification', namespaces)) self.identification=ows.ServiceIdentification(val,self.owscommon.namespace) # ServiceProvider val = self._exml.find(util.nspath_eval('ows:ServiceProvider', namespaces)) self.provider=ows.ServiceProvider(val,self.owscommon.namespace) # 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 = '%s%s' % (bind_url(self.url), 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) 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 self.records.iteritems(): #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)): 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) 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: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 if isinstance(self.request, basestring): # GET KVP self.response = urlopen(self.request, timeout=self.timeout).read() else: xml_post_url = self.url # Get correct POST 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) post_verbs = filter(lambda x: x.get('type').lower() == 'post', op.methods) if len(post_verbs) > 1: # Filter by constraints. We must match a PostEncoding of "XML" try: xml_post_url = next(x for x in filter(list, ([pv.get('url') for const in pv.get('constraints') if const.name.lower() == "postencoding" and 'xml' in map(lambda x: x.lower(), const.values)] for pv in post_verbs)))[0] except StopIteration: # Well, just use the first one. xml_post_url = post_verbs[0].get('url') elif len(post_verbs) == 1: xml_post_url = post_verbs[0].get('url') except: # no such luck, just go with xml_post_url pass 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(xml_post_url, self.request, self.lang, self.timeout) # parse result see if it's XML self._exml = etree.parse(StringIO.StringIO(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