Example #1
0
    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 get_records(self, maxrecords=50, **kwargs):
        """
        Send a GetRecords request. The results are stored in self.records property.

        :param kwargs: see OWSLib's getrecords2 (https://github.com/geopython/OWSLib/blob/master/owslib/csw.py).
        A hint: if "xml" argument is passed (raw WML request), other arguments are ignored. Also, if maxrecords exceeds
        50, getrecords2 is called multiple times to get maxrecords records (or less if less are found)
        """
        # has xml argument been passed?
        try:
            payload = kwargs['xml'].strip()
        except KeyError:
            payload = None

        # all 'csw:Record' dict from 'GetRecords' request pages
        all_records = OrderedDict()

        while True:
            # set kwargs' maxrecords according to how many records we want (doesn't matter if it exceeds 50)
            kwargs['maxrecords'] = min(maxrecords,
                                       maxrecords - len(all_records))

            # get next page by using OWSLib's getrecords2
            if payload is None:
                self.getrecords2(**kwargs)
            else:
                self.getrecords2(xml=payload)

            # store found records in all_records
            all_records.update(self.records)

            # stop if records reached limit
            if len(all_records) >= maxrecords:
                break

            next_record = self.results['nextrecord']
            # if next_record is "0", we got all records
            if next_record == 0:
                break

            # else, update start position
            if payload is None:
                kwargs['startposition'] = next_record
            else:
                payload_xml = etree.fromstring(payload)
                payload_xml.set('startPosition', str(next_record))
                payload = etree.tostring(payload_xml,
                                         pretty_print=True,
                                         encoding='unicode')

        self.records = all_records
Example #3
0
    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)
Example #4
0
File: csw.py Project: Geoneer/QGIS
    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)
Example #5
0
    def _buildMetadata(self, parse_remote_metadata=False):
        '''set up capabilities metadata objects:'''

        self.updateSequence = self._capabilities.attrib.get('updateSequence')

        # serviceIdentification metadata
        serviceelem = self._capabilities.find(
            nspath('Service', ns=WMS_NAMESPACE))
        self.identification = ServiceIdentification(serviceelem, self.version)

        # serviceProvider metadata
        self.provider = ServiceProvider(serviceelem)

        # serviceOperations metadata
        self.operations = []
        for elem in self._capabilities.find(
                nspath('Capability/Request', ns=WMS_NAMESPACE))[:]:
            self.operations.append(OperationMetadata(elem))

        # serviceContents metadata: our assumption is that services use a top-level
        # layer as a metadata organizer, nothing more.
        self.contents = OrderedDict()
        caps = self._capabilities.find(nspath('Capability', WMS_NAMESPACE))

        # recursively gather content metadata for all layer elements.
        # To the WebMapService.contents store only metadata of named layers.
        def gather_layers(parent_elem, parent_metadata):
            layers = []
            for index, elem in enumerate(
                    parent_elem.findall(nspath('Layer', WMS_NAMESPACE))):
                cm = ContentMetadata(
                    elem,
                    parent=parent_metadata,
                    index=index + 1,
                    parse_remote_metadata=parse_remote_metadata)
                if cm.id:
                    if cm.id in self.contents:
                        warnings.warn(
                            'Content metadata for layer "%s" already exists. Using child layer'
                            % cm.id)
                    layers.append(cm)
                    self.contents[cm.id] = cm
                cm.children = gather_layers(elem, cm)
            return layers

        gather_layers(caps, None)

        # exceptions
        self.exceptions = [
            f.text for f in self._capabilities.findall(
                nspath('Capability/Exception/Format', WMS_NAMESPACE))
        ]
Example #6
0
    def mundigetrecords2(self, xml):
        # removing unrelevant information from given payload
        payload = xml.strip()

        # all 'csw:Record' dictionnary from 'GetRecords' request pages
        all_records = OrderedDict()

        # getting first page (i.e. 'page0')
        self.getrecords2(xml=payload)
        all_records.update(self.records)
        page0 = lxml.etree.fromstring(self.response)

        sr_node = page0.find('csw:SearchResults', namespaces=mundi_nsmap)
        nb_total = int(sr_node.get("numberOfRecordsMatched"))
        nb_set = int(sr_node.get("numberOfRecordsReturned"))
        next_record = int(sr_node.get("nextRecord"))

        # calculation of page number
        if (nb_total == 0):
            nbPages = 1
        else:
            nbPages = ceil(nb_total / nb_set)

        # getting other/next pages (i.e. 'pageN')
        i = 1
        while (i < nbPages):
            # modifying payload with new start position
            node_p = lxml.etree.fromstring(payload)
            node_p.set('startPosition', str(next_record))
            payload = lxml.etree.tostring(node_p, pretty_print=True)
            self.getrecords2(xml=payload)
            pageN = lxml.etree.fromstring(self.response)
            sr_node = pageN.find('csw:SearchResults', namespaces=mundi_nsmap)
            next_record = int(sr_node.get("nextRecord"))
            all_records.update(self.records)
            # go next page
            i += 1
        self.records = all_records
Example #7
0
    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)
Example #8
0
    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)
Example #9
0
class CatalogueServiceWeb(object):
    """ csw request class """
    def __init__(self,
                 url,
                 lang='en-US',
                 version='3.0.0',
                 timeout=10,
                 skip_caps=False,
                 username=None,
                 password=None,
                 auth=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 '3.0.0')
        - 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
        - auth: instance of owslib.util.Authentication

        """
        if auth:
            if username:
                auth.username = username
            if password:
                auth.password = password
        self.url = util.clean_ows_url(url)
        self.lang = lang
        self.version = version
        self.timeout = timeout
        self.auth = auth or Authentication(username, password)
        self.service = 'CSW'
        self.exceptionreport = None
        self.owscommon = ows.OwsCommon('2.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:
                self.updateSequence = self._exml.getroot().attrib.get(
                    'updateSequence')

                # ServiceIdentification
                val = self._exml.find(
                    util.nspath_eval('ows200: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('ows200: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(
                            'ows200:OperationsMetadata/ows200:Operation',
                            namespaces)):  # noqa
                    self.operations.append(
                        ows.OperationsMetadata(elem, self.owscommon.namespace))
                self.constraints = {}
                for elem in self._exml.findall(
                        util.nspath_eval(
                            'ows200:OperationsMetadata/ows200:Constraint',
                            namespaces)):  # noqa
                    self.constraints[elem.attrib['name']] = ows.Constraint(
                        elem, self.owscommon.namespace)
                self.parameters = {}
                for elem in self._exml.findall(
                        util.nspath_eval(
                            'ows200:OperationsMetadata/ows200:Parameter',
                            namespaces)):  # noqa
                    self.parameters[elem.attrib['name']] = ows.Parameter(
                        elem, self.owscommon.namespace)

                # FilterCapabilities
                val = self._exml.find(
                    util.nspath_eval('fes:Filter_Capabilities', namespaces))
                self.filters = fes2.FilterCapabilities(val)

    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('csw30: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 = 'ValueReference'
        else:
            dtypename = 'ParameterName'

        etree.SubElement(node0,
                         util.nspath_eval('csw30:%s' % dtypename,
                                          namespaces)).text = dname

        self.request = node0

        self._invoke()

        if self.exceptionreport is None:
            self.results = {}

            val = self._exml.find(
                util.nspath_eval('csw30:DomainValues',
                                 namespaces)).attrib.get('type')
            self.results['type'] = util.testXMLValue(val, True)

            val = self._exml.find(
                util.nspath_eval('csw30:DomainValues/csw30:%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(
                        'csw30:DomainValues/csw30:ListOfValues/csw30:Value',
                        namespaces)):  # noqa
                self.results['values'].append(util.testXMLValue(f))

    def getrecordbyid(self,
                      id=[],
                      esn='full',
                      outputschema=namespaces['csw30'],
                      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/3.0.0')
        - 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 getrecords(self,
                   constraints=[],
                   sortby=None,
                   typenames='csw30:Record',
                   esn='summary',
                   outputschema=namespaces['csw30'],
                   format=outputformat,
                   startposition=0,
                   maxrecords=10,
                   cql=None,
                   xml=None):
        """

        Construct and process a  GetRecords request

        Parameters
        ----------

        - constraints: the list of constraints (OgcExpression from owslib.fes2 module)
        - sortby: an OGC SortBy object (SortBy from owslib.fes2 module)
        - typenames: the typeNames to query against (default is csw30:Record)
        - esn: the ElementSetName 'full', 'brief' or 'summary' (default is 'summary')
        - outputschema: the outputSchema (default is 'http://www.opengis.net/cat/csw/3.0.0')
        - 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

        """

        if xml is not None:
            if xml.startswith('<'):
                self.request = etree.fromstring(xml)
                val = self.request.find(
                    util.nspath_eval('csw30:Query/csw30: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:
                self.request = xml
        else:
            # construct request
            node0 = self._setrootelement('csw30:GetRecords')
            if etree.__name__ != 'lxml.etree':  # apply nsmap manually
                node0.set('xmlns:ows110', namespaces['ows110'])
                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)
            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('csw30:Query', namespaces))
            node1.set('typeNames', typenames)

            etree.SubElement(
                node1, util.nspath_eval('csw30:ElementSetName',
                                        namespaces)).text = esn

            if any([len(constraints) > 0, cql is not None]):
                node2 = etree.SubElement(
                    node1, util.nspath_eval('csw30:Constraint', namespaces))
                node2.set('version', '1.1.0')
                flt = fes2.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('csw30:CqlText',
                                                namespaces)).text = cql

            if sortby is not None and isinstance(sortby, fes2.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(
                    'csw30:SearchResults',
                    namespaces)).attrib.get('numberOfRecordsMatched')
            self.results['matches'] = int(util.testXMLValue(val, True))
            val = self._exml.find(
                util.nspath_eval(
                    'csw30:SearchResults',
                    namespaces)).attrib.get('numberOfRecordsReturned')
            self.results['returned'] = int(util.testXMLValue(val, True))
            val = self._exml.find(
                util.nspath_eval('csw30: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='csw30: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 'csw30: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('csw30: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('csw30:%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('csw30:RecordProperty', namespaces))
                    etree.SubElement(
                        node2,
                        util.nspath_eval('csw30:Name',
                                         namespaces)).text = propertyname
                    etree.SubElement(
                        node2,
                        util.nspath_eval('csw30:Value',
                                         namespaces)).text = propertyvalue
                    self._setconstraint(node1, None, 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('csw30: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('csw30:Source',
                                                 namespaces)).text = source
        etree.SubElement(node0,
                         util.nspath_eval('csw30:ResourceType',
                                          namespaces)).text = resourcetype
        if resourceformat is not None:
            etree.SubElement(
                node0, util.nspath_eval('csw30:ResourceFormat',
                                        namespaces)).text = resourceformat
        if harvestinterval is not None:
            etree.SubElement(
                node0, util.nspath_eval('csw30:HarvestInterval',
                                        namespaces)).text = harvestinterval
        if responsehandler is not None:
            etree.SubElement(
                node0, util.nspath_eval('csw30:ResponseHandler',
                                        namespaces)).text = responsehandler

        self.request = node0

        self._invoke()
        self.results = {}

        if self.exceptionreport is None:
            val = self._exml.find(
                util.nspath_eval('csw30: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('csw30: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 list(self.records.items()):
            # 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('csw30:InsertResult', namespaces)):
            for j in i.findall(
                    util.nspath_eval('csw30: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)
            for i in self._exml.findall(
                    './/' +
                    util.nspath_eval('gfc:FC_FeatureCatalogue', namespaces)):
                identifier = self._setidentifierkey(
                    util.testXMLValue(i.attrib['uuid'], attrib=True))
                self.records[identifier] = FC_FeatureCatalogue(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(
                    'csw30:%s' % self._setesnel(esn), namespaces)):
                val = i.find(util.nspath_eval('dc:identifier', namespaces))
                identifier = self._setidentifierkey(util.testXMLValue(val))
                self.records[identifier] = Csw30Record(i)

    def _parsetransactionsummary(self):
        val = self._exml.find(
            util.nspath_eval(
                'csw30:TransactionResponse/csw30: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('csw30:totalInserted', namespaces))
            self.results['inserted'] = int(util.testXMLValue(ts))
            ts = val.find(util.nspath_eval('csw30:totalUpdated', namespaces))
            self.results['updated'] = int(util.testXMLValue(ts))
            ts = val.find(util.nspath_eval('csw30: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='csw30: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('csw30:Constraint', namespaces))
            node0.set('version', '1.1.0')

            if identifier is not None:  # set identifier filter, overrides all other parameters
                flt = fes2.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('csw30:CqlText', namespaces))
                node1.text = cql
            else:  # construct a Filter request
                flt = fes2.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'):
            try:
                op = self.get_operation_by_name('getrecords')
                if isinstance(self.request, str):  # 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_url = post_verbs[0].get('url')
            except Exception:  # no such luck, just go with request_url
                pass

        if isinstance(self.request, str):  # GET KVP
            self.request = '%s%s' % (bind_url(request_url), self.request)
            self.response = openURL(self.request,
                                    None,
                                    'Get',
                                    timeout=self.timeout,
                                    auth=self.auth,
                                    headers={
                                        'Accept': outputformat
                                    }).read()
        else:
            self.request = cleanup_namespaces(self.request)
            # Add any namespaces used in the "typeNames" attribute of the
            # csw30:Query element to the query's xml namespaces.
            for query in self.request.findall(
                    util.nspath_eval('csw30: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 = add_namespaces(self.request, 'fes')

            self.request = util.element_to_string(self.request,
                                                  encoding='utf-8')

            self.response = http_post(request_url,
                                      self.request,
                                      self.lang,
                                      self.timeout,
                                      auth=self.auth)

        # 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('ows200:ExceptionReport', namespaces),
            util.nspath_eval('csw30:Capabilities', namespaces),
            util.nspath_eval('csw30:DescribeRecordResponse', namespaces),
            util.nspath_eval('csw30:GetDomainResponse', namespaces),
            util.nspath_eval('csw30:GetRecordsResponse', namespaces),
            util.nspath_eval('csw30:GetRecordByIdResponse', namespaces),
            util.nspath_eval('csw30:HarvestResponse', namespaces),
            util.nspath_eval('csw30:TransactionResponse', namespaces),
            util.nspath_eval('csw30:Record', 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('ows200:Exception', namespaces))
        if val is not None:
            raise ows.ExceptionReport(self._exml, self.owscommon.namespace)
        else:
            self.exceptionreport = None
Example #10
0
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'):
                for op in self.operations:
                    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')

            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
Example #11
0
    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)
Example #12
0
    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)
Example #13
0
    def getrecords2(
        self,
        constraints=[],
        sortby=None,
        typenames="csw:Record",
        esn="summary",
        outputschema=csw_namespaces["csw"],
        format=csw_outputformat,
        startposition=0,
        maxrecords=10,
        cql=None,
        xml=None,
        resulttype="results",
    ):
        if xml is not None:
            self.request = etree.fromstring(xml)
            val = self.request.find(
                util.nspath_eval("csw:Query/csw:ElementSetName",
                                 csw_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", csw_namespaces["ows"])
                node0.set("xmlns:gmd", csw_namespaces["gmd"])
                node0.set("xmlns:dif", csw_namespaces["dif"])
                node0.set("xmlns:fgdc", csw_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", csw_namespaces),
                csw_schema_location,
            )

            node1 = etree.SubElement(
                node0, util.nspath_eval("csw:Query", csw_namespaces))
            node1.set("typeNames", typenames)

            etree.SubElement(
                node1, util.nspath_eval("csw:ElementSetName",
                                        csw_namespaces)).text = esn

            if any([len(constraints) > 0, cql is not None]):
                node2 = etree.SubElement(
                    node1, util.nspath_eval("csw:Constraint", csw_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",
                                                csw_namespaces)).text = cql

            if sortby is not None and isinstance(sortby, fes.SortBy):
                node1.append(sortby.toXML())

            self.request = node0

        # print("Delta")
        self._invoke()

        if self.exceptionreport is None:
            self.results = {}

            # process search results attributes
            val = self._exml.find(
                util.nspath_eval(
                    "csw:SearchResults",
                    csw_namespaces)).attrib.get("numberOfRecordsMatched")
            self.results["matches"] = int(util.testXMLValue(val, True))
            val = self._exml.find(
                util.nspath_eval(
                    "csw:SearchResults",
                    csw_namespaces)).attrib.get("numberOfRecordsReturned")
            self.results["returned"] = int(util.testXMLValue(val, True))
            val = self._exml.find(
                util.nspath_eval("csw:SearchResults",
                                 csw_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)