1# -*- coding: utf-8 -*-
2# =================================================================
3#
4# Authors: Tom Kralidis <tomkralidis@gmail.com>
5#          Angelos Tzotsos <tzotsos@gmail.com>
6#
7# Copyright (c) 2015 Tom Kralidis
8# Copyright (c) 2015 Angelos Tzotsos
9#
10# Permission is hereby granted, free of charge, to any person
11# obtaining a copy of this software and associated documentation
12# files (the "Software"), to deal in the Software without
13# restriction, including without limitation the rights to use,
14# copy, modify, merge, publish, distribute, sublicense, and/or sell
15# copies of the Software, and to permit persons to whom the
16# Software is furnished to do so, subject to the following
17# conditions:
18#
19# The above copyright notice and this permission notice shall be
20# included in all copies or substantial portions of the Software.
21#
22# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
23# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
24# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
25# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
26# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
27# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
28# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
29# OTHER DEALINGS IN THE SOFTWARE.
30#
31# =================================================================
32
33import os
34from pycsw.core import config, util
35from pycsw.core.etree import etree
36from pycsw.plugins.profiles import profile
37
38CODELIST = 'http://www.isotc211.org/2005/resources/Codelist/gmxCodelists.xml'
39CODESPACE = 'ISOTC211/19115'
40
41class APISO(profile.Profile):
42    ''' APISO class '''
43    def __init__(self, model, namespaces, context):
44        self.context = context
45
46        self.namespaces = {
47            'apiso': 'http://www.opengis.net/cat/csw/apiso/1.0',
48            'gco': 'http://www.isotc211.org/2005/gco',
49            'gmd': 'http://www.isotc211.org/2005/gmd',
50            'srv': 'http://www.isotc211.org/2005/srv',
51            'xlink': 'http://www.w3.org/1999/xlink'
52        }
53
54        self.inspire_namespaces = {
55            'inspire_ds': 'http://inspire.ec.europa.eu/schemas/inspire_ds/1.0',
56            'inspire_common': 'http://inspire.ec.europa.eu/schemas/common/1.0'
57        }
58
59
60        self.repository = {
61            'gmd:MD_Metadata': {
62                'outputschema': 'http://www.isotc211.org/2005/gmd',
63                'queryables': {
64                    'SupportedISOQueryables': {
65                        'apiso:Subject': {'xpath': 'gmd:identificationInfo/gmd:MD_Identification/gmd:descriptiveKeywords/gmd:MD_Keywords/gmd:keyword/gco:CharacterString|gmd:identificationInfo/gmd:MD_DataIdentification/gmd:topicCategory/gmd:MD_TopicCategoryCode', 'dbcol': self.context.md_core_model['mappings']['pycsw:Keywords']},
66                        'apiso:Title': {'xpath': 'gmd:identificationInfo/gmd:MD_DataIdentification/gmd:citation/gmd:CI_Citation/gmd:title/gco:CharacterString', 'dbcol': self.context.md_core_model['mappings']['pycsw:Title']},
67                        'apiso:Abstract': {'xpath': 'gmd:identificationInfo/gmd:MD_DataIdentification/gmd:abstract/gco:CharacterString', 'dbcol': self.context.md_core_model['mappings']['pycsw:Abstract']},
68                        'apiso:Format': {'xpath': 'gmd:distributionInfo/gmd:MD_Distribution/gmd:distributionFormat/gmd:MD_Format/gmd:name/gco:CharacterString', 'dbcol': self.context.md_core_model['mappings']['pycsw:Format']},
69                        'apiso:Identifier': {'xpath': 'gmd:fileIdentifier/gco:CharacterString', 'dbcol': self.context.md_core_model['mappings']['pycsw:Identifier']},
70                        'apiso:Modified': {'xpath': 'gmd:dateStamp/gco:Date', 'dbcol': self.context.md_core_model['mappings']['pycsw:Modified']},
71                        'apiso:Type': {'xpath': 'gmd:hierarchyLevel/gmd:MD_ScopeCode', 'dbcol': self.context.md_core_model['mappings']['pycsw:Type']},
72                        'apiso:BoundingBox': {'xpath': 'apiso:BoundingBox', 'dbcol': self.context.md_core_model['mappings']['pycsw:BoundingBox']},
73                        'apiso:CRS': {'xpath': 'concat("urn:ogc:def:crs:","gmd:referenceSystemInfo/gmd:MD_ReferenceSystem/gmd:referenceSystemIdentifier/gmd:RS_Identifier/gmd:codeSpace/gco:CharacterString",":","gmd:referenceSystemInfo/gmd:MD_ReferenceSystem/gmd:referenceSystemIdentifier/gmd:RS_Identifier/gmd:version/gco:CharacterString",":","gmd:referenceSystemInfo/gmd:MD_ReferenceSystem/gmd:referenceSystemIdentifier/gmd:RS_Identifier/gmd:code/gco:CharacterString")', 'dbcol': self.context.md_core_model['mappings']['pycsw:CRS']},
74                        'apiso:AlternateTitle': {'xpath': 'gmd:identificationInfo/gmd:MD_DataIdentification/gmd:citation/gmd:CI_Citation/gmd:alternateTitle/gco:CharacterString', 'dbcol': self.context.md_core_model['mappings']['pycsw:AlternateTitle']},
75                        'apiso:RevisionDate': {'xpath': 'gmd:identificationInfo/gmd:MD_DataIdentification/gmd:citation/gmd:CI_Citation/gmd:date/gmd:CI_Date[gmd:dateType/gmd:CI_DateTypeCode/@codeListValue="revision"]/gmd:date/gco:Date', 'dbcol': self.context.md_core_model['mappings']['pycsw:RevisionDate']},
76                        'apiso:CreationDate': {'xpath': 'gmd:identificationInfo/gmd:MD_DataIdentification/gmd:citation/gmd:CI_Citation/gmd:date/gmd:CI_Date[gmd:dateType/gmd:CI_DateTypeCode/@codeListValue="creation"]/gmd:date/gco:Date', 'dbcol': self.context.md_core_model['mappings']['pycsw:CreationDate']},
77                        'apiso:PublicationDate': {'xpath': 'gmd:identificationInfo/gmd:MD_DataIdentification/gmd:citation/gmd:CI_Citation/gmd:date/gmd:CI_Date[gmd:dateType/gmd:CI_DateTypeCode/@codeListValue="publication"]/gmd:date/gco:Date', 'dbcol': self.context.md_core_model['mappings']['pycsw:PublicationDate']},
78                        'apiso:OrganisationName': {'xpath': 'gmd:identificationInfo/gmd:MD_DataIdentification/gmd:pointOfContact/gmd:CI_ResponsibleParty/gmd:organisationName/gco:CharacterString', 'dbcol': self.context.md_core_model['mappings']['pycsw:OrganizationName']},
79                        'apiso:HasSecurityConstraints': {'xpath': 'gmd:identificationInfo/gmd:MD_DataIdentification/gmd:resourceConstraints/gmd:MD_SecurityConstraints', 'dbcol': self.context.md_core_model['mappings']['pycsw:SecurityConstraints']},
80                        'apiso:Language': {'xpath': 'gmd:language/gmd:LanguageCode|gmd:language/gco:CharacterString', 'dbcol': self.context.md_core_model['mappings']['pycsw:Language']},
81                        'apiso:ParentIdentifier': {'xpath': 'gmd:parentIdentifier/gco:CharacterString', 'dbcol': self.context.md_core_model['mappings']['pycsw:ParentIdentifier']},
82                        'apiso:KeywordType': {'xpath': 'gmd:identificationInfo/gmd:MD_DataIdentification/gmd:descriptiveKeywords/gmd:MD_Keywords/gmd:type/gmd:MD_KeywordTypeCode', 'dbcol': self.context.md_core_model['mappings']['pycsw:KeywordType']},
83                        'apiso:TopicCategory': {'xpath': 'gmd:identificationInfo/gmd:MD_DataIdentification/gmd:topicCategory/gmd:MD_TopicCategoryCode', 'dbcol': self.context.md_core_model['mappings']['pycsw:TopicCategory']},
84                        'apiso:ResourceLanguage': {'xpath': 'gmd:identificationInfo/gmd:MD_DataIdentification/gmd:citation/gmd:CI_Citation/gmd:identifier/gmd:code/gmd:MD_LanguageTypeCode', 'dbcol': self.context.md_core_model['mappings']['pycsw:ResourceLanguage']},
85                        'apiso:GeographicDescriptionCode': {'xpath': 'gmd:identificationInfo/gmd:MD_DataIdentification/gmd:extent/gmd:EX_Extent/gmd:geographicElement/gmd:EX_GeographicDescription/gmd:geographicIdentifier/gmd:MD_Identifier/gmd:code/gco:CharacterString', 'dbcol': self.context.md_core_model['mappings']['pycsw:GeographicDescriptionCode']},
86                        'apiso:Denominator': {'xpath': 'gmd:identificationInfo/gmd:MD_DataIdentification/gmd:spatialResolution/gmd:MD_Resolution/gmd:equivalentScale/gmd:MD_RepresentativeFraction/gmd:denominator/gco:Integer', 'dbcol': self.context.md_core_model['mappings']['pycsw:Denominator']},
87                        'apiso:DistanceValue': {'xpath': 'gmd:identificationInfo/gmd:MD_DataIdentification/gmd:spatialResolution/gmd:MD_Resolution/gmd:distance/gco:Distance', 'dbcol': self.context.md_core_model['mappings']['pycsw:DistanceValue']},
88                        'apiso:DistanceUOM': {'xpath': 'gmd:identificationInfo/gmd:MD_DataIdentification/gmd:spatialResolution/gmd:MD_Resolution/gmd:distance/gco:Distance/@uom', 'dbcol': self.context.md_core_model['mappings']['pycsw:DistanceUOM']},
89                        'apiso:TempExtent_begin': {'xpath': 'gmd:identificationInfo/gmd:MD_DataIdentification/gmd:extent/gmd:EX_Extent/gmd:temporalElement/gmd:EX_TemporalExtent/gmd:extent/gml:TimePeriod/gml:beginPosition', 'dbcol': self.context.md_core_model['mappings']['pycsw:TempExtent_begin']},
90                        'apiso:TempExtent_end': {'xpath': 'gmd:identificationInfo/gmd:MD_DataIdentification/gmd:extent/gmd:EX_Extent/gmd:temporalElement/gmd:EX_TemporalExtent/gmd:extent/gml:TimePeriod/gml:endPosition', 'dbcol': self.context.md_core_model['mappings']['pycsw:TempExtent_end']},
91                        'apiso:AnyText': {'xpath': '//', 'dbcol': self.context.md_core_model['mappings']['pycsw:AnyText']},
92                        'apiso:ServiceType': {'xpath': 'gmd:identificationInfo/srv:SV_ServiceIdentification/srv:serviceType/gco:LocalName', 'dbcol': self.context.md_core_model['mappings']['pycsw:ServiceType']},
93                        'apiso:ServiceTypeVersion': {'xpath': 'gmd:identificationInfo/srv:SV_ServiceIdentification/srv:serviceTypeVersion/gco:CharacterString', 'dbcol': self.context.md_core_model['mappings']['pycsw:ServiceTypeVersion']},
94                        'apiso:Operation': {'xpath': 'gmd:identificationInfo/srv:SV_ServiceIdentification/srv:containsOperations/srv:SV_OperationMetadata/srv:operationName/gco:CharacterString', 'dbcol': self.context.md_core_model['mappings']['pycsw:Operation']},
95                        'apiso:CouplingType': {'xpath': 'gmd:identificationInfo/srv:SV_ServiceIdentification/srv:couplingType/srv:SV_CouplingType', 'dbcol': self.context.md_core_model['mappings']['pycsw:CouplingType']},
96                        'apiso:OperatesOn': {'xpath': 'gmd:identificationInfo/srv:SV_ServiceIdentification/srv:operatesOn/gmd:MD_DataIdentification/gmd:citation/gmd:CI_Citation/gmd:identifier/gmd:RS_Identifier/gmd:code/gco:CharacterString', 'dbcol': self.context.md_core_model['mappings']['pycsw:OperatesOn']},
97                        'apiso:OperatesOnIdentifier': {'xpath': 'gmd:identificationInfo/srv:SV_ServiceIdentification/srv:coupledResource/srv:SV_CoupledResource/srv:identifier/gco:CharacterString', 'dbcol': self.context.md_core_model['mappings']['pycsw:OperatesOnIdentifier']},
98                        'apiso:OperatesOnName': {'xpath': 'gmd:identificationInfo/srv:SV_ServiceIdentification/srv:coupledResource/srv:SV_CoupledResource/srv:operationName/gco:CharacterString', 'dbcol': self.context.md_core_model['mappings']['pycsw:OperatesOnName']},
99                    },
100                    'AdditionalQueryables': {
101                        'apiso:Degree': {'xpath': 'gmd:dataQualityInfo/gmd:DQ_DataQuality/gmd:report/gmd:DQ_DomainConsistency/gmd:result/gmd:DQ_ConformanceResult/gmd:pass/gco:Boolean', 'dbcol': self.context.md_core_model['mappings']['pycsw:Degree']},
102                        'apiso:AccessConstraints': {'xpath': 'gmd:identificationInfo/gmd:MD_DataIdentification/gmd:resourceConstraints/gmd:MD_LegalConstraints/gmd:accessConstraints/gmd:MD_RestrictionCode', 'dbcol': self.context.md_core_model['mappings']['pycsw:AccessConstraints']},
103                        'apiso:OtherConstraints': {'xpath': 'gmd:identificationInfo/gmd:MD_DataIdentification/gmd:resourceConstraints/gmd:MD_LegalConstraints/gmd:otherConstraints/gco:CharacterString', 'dbcol': self.context.md_core_model['mappings']['pycsw:OtherConstraints']},
104                        'apiso:Classification': {'xpath': 'gmd:identificationInfo/gmd:MD_DataIdentification/gmd:resourceConstraints/gmd:MD_LegalConstraints/gmd:accessConstraints/gmd:MD_ClassificationCode', 'dbcol': self.context.md_core_model['mappings']['pycsw:Classification']},
105                        'apiso:ConditionApplyingToAccessAndUse': {'xpath': 'gmd:identificationInfo/gmd:MD_DataIdentification/gmd:useLimitation/gco:CharacterString', 'dbcol': self.context.md_core_model['mappings']['pycsw:ConditionApplyingToAccessAndUse']},
106                        'apiso:Lineage': {'xpath': 'gmd:dataQualityInfo/gmd:DQ_DataQuality/gmd:lineage/gmd:LI_Lineage/gmd:statement/gco:CharacterString', 'dbcol': self.context.md_core_model['mappings']['pycsw:Lineage']},
107                        'apiso:ResponsiblePartyRole': {'xpath': 'gmd:contact/gmd:CI_ResponsibleParty/gmd:role/gmd:CI_RoleCode', 'dbcol': self.context.md_core_model['mappings']['pycsw:ResponsiblePartyRole']},
108                        'apiso:SpecificationTitle': {'xpath': 'gmd:dataQualityInfo/gmd:DQ_DataQuality/gmd:report/gmd:DQ_DomainConsistency/gmd:result/gmd:DQ_ConformanceResult/gmd:specification/gmd:CI_Citation/gmd:title/gco:CharacterString', 'dbcol': self.context.md_core_model['mappings']['pycsw:SpecificationTitle']},
109                        'apiso:SpecificationDate': {'xpath': 'gmd:dataQualityInfo/gmd:DQ_DataQuality/gmd:report/gmd:DQ_DomainConsistency/gmd:result/gmd:DQ_ConformanceResult/gmd:specification/gmd:CI_Citation/gmd:date/gmd:CI_Date/gmd:date/gco:Date', 'dbcol': self.context.md_core_model['mappings']['pycsw:SpecificationDate']},
110                        'apiso:SpecificationDateType': {'xpath': 'gmd:dataQualityInfo/gmd:DQ_DataQuality/gmd:report/gmd:DQ_DomainConsistency/gmd:result/gmd:DQ_ConformanceResult/gmd:specification/gmd:CI_Citation/gmd:date/gmd:CI_Date/gmd:dateType/gmd:CI_DateTypeCode', 'dbcol': self.context.md_core_model['mappings']['pycsw:SpecificationDateType']},
111                        'apiso:Creator': {'xpath': 'gmd:identificationInfo/gmd:MD_DataIdentification/gmd:pointOfContact/gmd:CI_ResponsibleParty/gmd:organisationName[gmd:role/gmd:CI_RoleCode/@codeListValue="originator"]/gco:CharacterString', 'dbcol': self.context.md_core_model['mappings']['pycsw:Creator']},
112                        'apiso:Publisher': {'xpath': 'gmd:identificationInfo/gmd:MD_DataIdentification/gmd:pointOfContact/gmd:CI_ResponsibleParty/gmd:organisationName[gmd:role/gmd:CI_RoleCode/@codeListValue="publisher"]/gco:CharacterString', 'dbcol': self.context.md_core_model['mappings']['pycsw:Publisher']},
113                        'apiso:Contributor': {'xpath': 'gmd:identificationInfo/gmd:MD_DataIdentification/gmd:pointOfContact/gmd:CI_ResponsibleParty/gmd:organisationName[gmd:role/gmd:CI_RoleCode/@codeListValue="contributor"]/gco:CharacterString', 'dbcol': self.context.md_core_model['mappings']['pycsw:Contributor']},
114                        'apiso:Relation': {'xpath': 'gmd:identificationInfo/gmd:MD_Data_Identification/gmd:aggregationInfo', 'dbcol': self.context.md_core_model['mappings']['pycsw:Relation']}
115                    }
116                },
117                'mappings': {
118                    'csw:Record': {
119                        # map APISO queryables to DC queryables
120                        'apiso:Title': 'dc:title',
121                        'apiso:Creator': 'dc:creator',
122                        'apiso:Subject': 'dc:subject',
123                        'apiso:Abstract': 'dct:abstract',
124                        'apiso:Publisher': 'dc:publisher',
125                        'apiso:Contributor': 'dc:contributor',
126                        'apiso:Modified': 'dct:modified',
127                        #'apiso:Date': 'dc:date',
128                        'apiso:Type': 'dc:type',
129                        'apiso:Format': 'dc:format',
130                        'apiso:Language': 'dc:language',
131                        'apiso:Relation': 'dc:relation',
132                        'apiso:AccessConstraints': 'dc:rights',
133                    }
134                }
135            }
136        }
137
138        profile.Profile.__init__(self,
139            name='apiso',
140            version='1.0.0',
141            title='ISO Metadata Application Profile',
142            url='http://portal.opengeospatial.org/files/?artifact_id=21460',
143            namespace=self.namespaces['gmd'],
144            typename='gmd:MD_Metadata',
145            outputschema=self.namespaces['gmd'],
146            prefixes=['apiso', 'gmd'],
147            model=model,
148            core_namespaces=namespaces,
149            added_namespaces=self.namespaces,
150            repository=self.repository['gmd:MD_Metadata'])
151
152    def extend_core(self, model, namespaces, config):
153        ''' Extend core configuration '''
154
155        # update INSPIRE vars
156        self.context.namespaces.update(self.inspire_namespaces)
157
158        # update harvest resource types with WMS, since WMS is not a typename,
159        if 'Harvest' in model['operations']:
160            model['operations']['Harvest']['parameters']['ResourceType']['values'].append('http://www.isotc211.org/schemas/2005/gmd/')
161
162        # set INSPIRE config
163        if config.has_section('metadata:inspire') and config.has_option('metadata:inspire', 'enabled') and config.get('metadata:inspire', 'enabled') == 'true':
164            self.inspire_config = {}
165            self.inspire_config['languages_supported'] = config.get('metadata:inspire', 'languages_supported')
166            self.inspire_config['default_language'] = config.get('metadata:inspire', 'default_language')
167            self.inspire_config['date'] = config.get('metadata:inspire', 'date')
168            self.inspire_config['gemet_keywords'] = config.get('metadata:inspire', 'gemet_keywords')
169            self.inspire_config['conformity_service'] = config.get('metadata:inspire', 'conformity_service')
170            self.inspire_config['contact_name'] = config.get('metadata:inspire', 'contact_name')
171            self.inspire_config['contact_email'] = config.get('metadata:inspire', 'contact_email')
172            self.inspire_config['temp_extent'] = config.get('metadata:inspire', 'temp_extent')
173        else:
174            self.inspire_config = None
175
176        self.ogc_schemas_base = config.get('server', 'ogc_schemas_base')
177        self.url = config.get('server', 'url')
178
179    def check_parameters(self, kvp):
180        '''Check for Language parameter in GetCapabilities request'''
181
182        if self.inspire_config is not None:
183            result = None
184            if 'language' not in kvp:
185                self.inspire_config['current_language'] = self.inspire_config['default_language']
186            else:
187                if kvp['language'] not in self.inspire_config['languages_supported'].split(','):
188                    text = 'Requested Language not supported, Supported languages: %s' % self.inspire_config['languages_supported']
189                    return {'error': 'true', 'locator': 'language', 'code': 'InvalidParameterValue', 'text': text}
190                else:
191                    self.inspire_config['current_language'] = kvp['language']
192                    return None
193            return None
194        return None
195
196    def get_extendedcapabilities(self):
197        ''' Add child to ows:OperationsMetadata Element '''
198
199        if self.inspire_config is not None:
200
201            ex_caps = etree.Element(
202                util.nspath_eval('inspire_ds:ExtendedCapabilities', self.inspire_namespaces))
203
204            ex_caps.attrib[util.nspath_eval('xsi:schemaLocation', self.context.namespaces)] = \
205            '%s %s/inspire_ds.xsd' % \
206            (self.inspire_namespaces['inspire_ds'], self.inspire_namespaces['inspire_ds'])
207
208            # Resource Locator
209            res_loc = etree.SubElement(ex_caps,
210            util.nspath_eval('inspire_common:ResourceLocator', self.inspire_namespaces))
211
212            etree.SubElement(res_loc,
213            util.nspath_eval('inspire_common:URL', self.inspire_namespaces)).text = '%sservice=CSW&version=2.0.2&request=GetCapabilities' % (util.bind_url(self.url))
214
215            etree.SubElement(res_loc,
216            util.nspath_eval('inspire_common:MediaType', self.inspire_namespaces)).text = 'application/xml'
217
218            # Resource Type
219            etree.SubElement(ex_caps,
220            util.nspath_eval('inspire_common:ResourceType', self.inspire_namespaces)).text = 'service'
221
222            # Temporal Reference
223            temp_ref = etree.SubElement(ex_caps,
224            util.nspath_eval('inspire_common:TemporalReference', self.inspire_namespaces))
225
226            temp_extent = etree.SubElement(temp_ref,
227            util.nspath_eval('inspire_common:TemporalExtent', self.inspire_namespaces))
228
229            val = self.inspire_config['temp_extent'].split('/')
230
231            if len(val) == 1:
232                etree.SubElement(temp_extent,
233                util.nspath_eval('inspire_common:IndividualDate', self.inspire_namespaces)).text = val[0]
234
235            else:
236                interval_dates = etree.SubElement(temp_extent,
237                util.nspath_eval('inspire_common:IntervalOfDates', self.inspire_namespaces))
238
239                etree.SubElement(interval_dates,
240                util.nspath_eval('inspire_common:StartingDate', self.inspire_namespaces)).text = val[0]
241
242                etree.SubElement(interval_dates,
243                util.nspath_eval('inspire_common:EndDate', self.inspire_namespaces)).text = val[1]
244
245            # Conformity - service
246            cfm = etree.SubElement(ex_caps,
247            util.nspath_eval('inspire_common:Conformity', self.inspire_namespaces))
248
249            spec = etree.SubElement(cfm,
250            util.nspath_eval('inspire_common:Specification', self.inspire_namespaces))
251
252            spec.attrib[util.nspath_eval('xsi:type', self.context.namespaces)] =  'inspire_common:citationInspireInteroperabilityRegulation_eng'
253
254            etree.SubElement(spec,
255            util.nspath_eval('inspire_common:Title', self.inspire_namespaces)).text = 'COMMISSION REGULATION (EU) No 1089/2010 of 23 November 2010 implementing Directive 2007/2/EC of the European Parliament and of the Council as regards interoperability of spatial data sets and services'
256
257            etree.SubElement(spec,
258            util.nspath_eval('inspire_common:DateOfPublication', self.inspire_namespaces)).text = '2010-12-08'
259
260            etree.SubElement(spec,
261            util.nspath_eval('inspire_common:URI', self.inspire_namespaces)).text = 'OJ:L:2010:323:0011:0102:EN:PDF'
262
263            spec_loc = etree.SubElement(spec,
264            util.nspath_eval('inspire_common:ResourceLocator', self.inspire_namespaces))
265
266            etree.SubElement(spec_loc,
267            util.nspath_eval('inspire_common:URL', self.inspire_namespaces)).text = 'http://eur-lex.europa.eu/LexUriServ/LexUriServ.do?uri=OJ:L:2010:323:0011:0102:EN:PDF'
268
269            etree.SubElement(spec_loc,
270            util.nspath_eval('inspire_common:MediaType', self.inspire_namespaces)).text = 'application/pdf'
271
272            spec = etree.SubElement(cfm,
273            util.nspath_eval('inspire_common:Degree', self.inspire_namespaces)).text = self.inspire_config['conformity_service']
274
275            # Metadata Point of Contact
276            poc = etree.SubElement(ex_caps,
277            util.nspath_eval('inspire_common:MetadataPointOfContact', self.inspire_namespaces))
278
279            etree.SubElement(poc,
280            util.nspath_eval('inspire_common:OrganisationName', self.inspire_namespaces)).text = self.inspire_config['contact_name']
281
282            etree.SubElement(poc,
283            util.nspath_eval('inspire_common:EmailAddress', self.inspire_namespaces)).text = self.inspire_config['contact_email']
284
285            # Metadata Date
286            etree.SubElement(ex_caps,
287            util.nspath_eval('inspire_common:MetadataDate', self.inspire_namespaces)).text = self.inspire_config['date']
288
289            # Spatial Data Service Type
290            etree.SubElement(ex_caps,
291            util.nspath_eval('inspire_common:SpatialDataServiceType', self.inspire_namespaces)).text = 'discovery'
292
293            # Mandatory Keyword
294            mkey = etree.SubElement(ex_caps,
295            util.nspath_eval('inspire_common:MandatoryKeyword', self.inspire_namespaces))
296
297            mkey.attrib[util.nspath_eval('xsi:type', self.context.namespaces)] = 'inspire_common:classificationOfSpatialDataService'
298
299            etree.SubElement(mkey,
300            util.nspath_eval('inspire_common:KeywordValue', self.inspire_namespaces)).text = 'infoCatalogueService'
301
302            # Gemet Keywords
303
304            for gkw in self.inspire_config['gemet_keywords'].split(','):
305                gkey = etree.SubElement(ex_caps,
306                util.nspath_eval('inspire_common:Keyword', self.inspire_namespaces))
307
308                gkey.attrib[util.nspath_eval('xsi:type', self.context.namespaces)] = 'inspire_common:inspireTheme_eng'
309
310                ocv = etree.SubElement(gkey,
311                util.nspath_eval('inspire_common:OriginatingControlledVocabulary', self.inspire_namespaces))
312
313                etree.SubElement(ocv,
314                util.nspath_eval('inspire_common:Title', self.inspire_namespaces)).text = 'GEMET - INSPIRE themes'
315
316                etree.SubElement(ocv,
317                util.nspath_eval('inspire_common:DateOfPublication', self.inspire_namespaces)).text = '2008-06-01'
318
319                etree.SubElement(gkey,
320                util.nspath_eval('inspire_common:KeywordValue', self.inspire_namespaces)).text = gkw
321
322            # Languages
323            slang = etree.SubElement(ex_caps,
324            util.nspath_eval('inspire_common:SupportedLanguages', self.inspire_namespaces))
325
326            dlang = etree.SubElement(slang,
327            util.nspath_eval('inspire_common:DefaultLanguage', self.inspire_namespaces))
328
329            etree.SubElement(dlang,
330            util.nspath_eval('inspire_common:Language', self.inspire_namespaces)).text = self.inspire_config['default_language']
331
332            for l in self.inspire_config['languages_supported'].split(','):
333                lang = etree.SubElement(slang,
334                util.nspath_eval('inspire_common:SupportedLanguage', self.inspire_namespaces))
335
336                etree.SubElement(lang,
337                util.nspath_eval('inspire_common:Language', self.inspire_namespaces)).text = l
338
339            clang = etree.SubElement(ex_caps,
340            util.nspath_eval('inspire_common:ResponseLanguage', self.inspire_namespaces))
341            etree.SubElement(clang,
342            util.nspath_eval('inspire_common:Language', self.inspire_namespaces)).text = self.inspire_config['current_language']
343
344            return ex_caps
345
346    def get_schemacomponents(self):
347        ''' Return schema components as lxml.etree.Element list '''
348
349        node1 = etree.Element(
350        util.nspath_eval('csw:SchemaComponent', self.context.namespaces),
351        schemaLanguage='XMLSCHEMA', targetNamespace=self.namespace,
352        parentSchema='gmd.xsd')
353
354        schema_file = os.path.join(self.context.pycsw_home, 'plugins',
355                                   'profiles', 'apiso', 'schemas', 'ogc',
356                                   'iso', '19139', '20060504', 'gmd',
357                                   'identification.xsd')
358
359        schema = etree.parse(schema_file, self.context.parser).getroot()
360
361        node1.append(schema)
362
363        node2 = etree.Element(
364        util.nspath_eval('csw:SchemaComponent', self.context.namespaces),
365        schemaLanguage='XMLSCHEMA', targetNamespace=self.namespace,
366        parentSchema='gmd.xsd')
367
368        schema_file = os.path.join(self.context.pycsw_home, 'plugins',
369                                   'profiles', 'apiso', 'schemas', 'ogc',
370                                   'iso', '19139', '20060504', 'srv',
371                                   'serviceMetadata.xsd')
372
373        schema = etree.parse(schema_file, self.context.parser).getroot()
374
375        node2.append(schema)
376
377        return [node1, node2]
378
379    def check_getdomain(self, kvp):
380        '''Perform extra profile specific checks in the GetDomain request'''
381        return None
382
383    def write_record(self, result, esn, outputschema, queryables, caps=None):
384        ''' Return csw:SearchResults child as lxml.etree.Element '''
385        typename = util.getqattr(result, self.context.md_core_model['mappings']['pycsw:Typename'])
386        is_iso_anyway = False
387
388        xml_blob = util.getqattr(result, self.context.md_core_model['mappings']['pycsw:XML'])
389
390        #xml_blob_decoded = bytes.fromhex(xml_blob[2:]).decode('utf-8')
391
392        if isinstance(xml_blob, bytes):
393            iso_string = b'<gmd:MD_Metadata>'
394        else:
395            iso_string = '<gmd:MD_Metadata>'
396
397        if caps is None and xml_blob is not None and xml_blob.startswith(iso_string):
398            is_iso_anyway = True
399
400        if (esn == 'full' and (typename == 'gmd:MD_Metadata' or is_iso_anyway)):
401            # dump record as is and exit
402            return etree.fromstring(xml_blob, self.context.parser)
403
404        node = etree.Element(util.nspath_eval('gmd:MD_Metadata', self.namespaces))
405        node.attrib[util.nspath_eval('xsi:schemaLocation', self.context.namespaces)] = \
406        '%s %s/csw/2.0.2/profiles/apiso/1.0.0/apiso.xsd' % (self.namespace, self.ogc_schemas_base)
407
408        # identifier
409        idval = util.getqattr(result, self.context.md_core_model['mappings']['pycsw:Identifier'])
410
411        identifier = etree.SubElement(node, util.nspath_eval('gmd:fileIdentifier', self.namespaces))
412        etree.SubElement(identifier, util.nspath_eval('gco:CharacterString', self.namespaces)).text = idval
413
414        if esn in ['summary', 'full']:
415            # language
416            val = util.getqattr(result, queryables['apiso:Language']['dbcol'])
417
418            lang = etree.SubElement(node, util.nspath_eval('gmd:language', self.namespaces))
419            etree.SubElement(lang, util.nspath_eval('gco:CharacterString', self.namespaces)).text = val
420
421        # hierarchyLevel
422        mtype = util.getqattr(result, queryables['apiso:Type']['dbcol']) or None
423
424        if mtype is not None:
425            if mtype == 'http://purl.org/dc/dcmitype/Dataset':
426                mtype = 'dataset'
427            hierarchy = etree.SubElement(node, util.nspath_eval('gmd:hierarchyLevel', self.namespaces))
428            hierarchy.append(_write_codelist_element('gmd:MD_ScopeCode', mtype, self.namespaces))
429
430        if esn in ['summary', 'full']:
431            # contact
432            contact = etree.SubElement(node, util.nspath_eval('gmd:contact', self.namespaces))
433            if caps is not None:
434                CI_resp = etree.SubElement(contact, util.nspath_eval('gmd:CI_ResponsibleParty', self.namespaces))
435                if hasattr(caps.provider.contact, 'name'):
436                    ind_name = etree.SubElement(CI_resp, util.nspath_eval('gmd:individualName', self.namespaces))
437                    etree.SubElement(ind_name, util.nspath_eval('gco:CharacterString', self.namespaces)).text = caps.provider.contact.name
438                if hasattr(caps.provider.contact, 'organization'):
439                    if caps.provider.contact.organization is not None:
440                        org_val = caps.provider.contact.organization
441                    else:
442                        org_val = caps.provider.name
443                    org_name = etree.SubElement(CI_resp, util.nspath_eval('gmd:organisationName', self.namespaces))
444                    etree.SubElement(org_name, util.nspath_eval('gco:CharacterString', self.namespaces)).text = org_val
445                if hasattr(caps.provider.contact, 'position'):
446                    pos_name = etree.SubElement(CI_resp, util.nspath_eval('gmd:positionName', self.namespaces))
447                    etree.SubElement(pos_name, util.nspath_eval('gco:CharacterString', self.namespaces)).text = caps.provider.contact.position
448                contact_info = etree.SubElement(CI_resp, util.nspath_eval('gmd:contactInfo', self.namespaces))
449                ci_contact = etree.SubElement(contact_info, util.nspath_eval('gmd:CI_Contact', self.namespaces))
450                if hasattr(caps.provider.contact, 'phone'):
451                    phone = etree.SubElement(ci_contact, util.nspath_eval('gmd:phone', self.namespaces))
452                    ci_phone = etree.SubElement(phone, util.nspath_eval('gmd:CI_Telephone', self.namespaces))
453                    voice = etree.SubElement(ci_phone, util.nspath_eval('gmd:voice', self.namespaces))
454                    etree.SubElement(voice, util.nspath_eval('gco:CharacterString', self.namespaces)).text = caps.provider.contact.phone
455                    if hasattr(caps.provider.contact, 'fax'):
456                        fax = etree.SubElement(ci_phone, util.nspath_eval('gmd:facsimile', self.namespaces))
457                        etree.SubElement(fax, util.nspath_eval('gco:CharacterString', self.namespaces)).text = caps.provider.contact.fax
458                address = etree.SubElement(ci_contact, util.nspath_eval('gmd:address', self.namespaces))
459                ci_address = etree.SubElement(address, util.nspath_eval('gmd:CI_Address', self.namespaces))
460                if hasattr(caps.provider.contact, 'address'):
461                    delivery_point = etree.SubElement(ci_address, util.nspath_eval('gmd:deliveryPoint', self.namespaces))
462                    etree.SubElement(delivery_point, util.nspath_eval('gco:CharacterString', self.namespaces)).text = caps.provider.contact.address
463                if hasattr(caps.provider.contact, 'city'):
464                    city = etree.SubElement(ci_address, util.nspath_eval('gmd:city', self.namespaces))
465                    etree.SubElement(city, util.nspath_eval('gco:CharacterString', self.namespaces)).text = caps.provider.contact.city
466                if hasattr(caps.provider.contact, 'region'):
467                    admin_area = etree.SubElement(ci_address, util.nspath_eval('gmd:administrativeArea', self.namespaces))
468                    etree.SubElement(admin_area, util.nspath_eval('gco:CharacterString', self.namespaces)).text = caps.provider.contact.region
469                if hasattr(caps.provider.contact, 'postcode'):
470                    postal_code = etree.SubElement(ci_address, util.nspath_eval('gmd:postalCode', self.namespaces))
471                    etree.SubElement(postal_code, util.nspath_eval('gco:CharacterString', self.namespaces)).text = caps.provider.contact.postcode
472                if hasattr(caps.provider.contact, 'country'):
473                    country = etree.SubElement(ci_address, util.nspath_eval('gmd:country', self.namespaces))
474                    etree.SubElement(country, util.nspath_eval('gco:CharacterString', self.namespaces)).text = caps.provider.contact.country
475                if hasattr(caps.provider.contact, 'email'):
476                    email = etree.SubElement(ci_address, util.nspath_eval('gmd:electronicMailAddress', self.namespaces))
477                    etree.SubElement(email, util.nspath_eval('gco:CharacterString', self.namespaces)).text = caps.provider.contact.email
478
479                contact_url = None
480                if hasattr(caps.provider, 'url'):
481                    contact_url = caps.provider.url
482                if hasattr(caps.provider.contact, 'url') and caps.provider.contact.url is not None:
483                    contact_url = caps.provider.contact.url
484
485                if contact_url is not None:
486                    online_resource = etree.SubElement(ci_contact, util.nspath_eval('gmd:onlineResource', self.namespaces))
487                    gmd_linkage = etree.SubElement(online_resource, util.nspath_eval('gmd:linkage', self.namespaces))
488                    etree.SubElement(gmd_linkage, util.nspath_eval('gmd:URL', self.namespaces)).text = contact_url
489
490                if hasattr(caps.provider.contact, 'role'):
491                    role = etree.SubElement(CI_resp, util.nspath_eval('gmd:role', self.namespaces))
492                    role_val = caps.provider.contact.role
493                    if role_val is None:
494                        role_val = 'pointOfContact'
495                    etree.SubElement(role, util.nspath_eval('gmd:CI_RoleCode', self.namespaces), codeListValue=role_val, codeList='%s#CI_RoleCode' % CODELIST).text = role_val
496            else:
497                val = util.getqattr(result, queryables['apiso:OrganisationName']['dbcol'])
498                if val:
499                    CI_resp = etree.SubElement(contact, util.nspath_eval('gmd:CI_ResponsibleParty', self.namespaces))
500                    org_name = etree.SubElement(CI_resp, util.nspath_eval('gmd:organisationName', self.namespaces))
501                    etree.SubElement(org_name, util.nspath_eval('gco:CharacterString', self.namespaces)).text = val
502
503            # date
504            val = util.getqattr(result, queryables['apiso:Modified']['dbcol'])
505            date = etree.SubElement(node, util.nspath_eval('gmd:dateStamp', self.namespaces))
506            if val and val.find('T') != -1:
507                dateel = 'gco:DateTime'
508            else:
509                dateel = 'gco:Date'
510            etree.SubElement(date, util.nspath_eval(dateel, self.namespaces)).text = val
511
512            metadatastandardname = 'ISO19115'
513            metadatastandardversion = '2003/Cor.1:2006'
514
515            if mtype == 'service':
516                metadatastandardname = 'ISO19119'
517                metadatastandardversion = '2005/PDAM 1'
518
519            # metadata standard name
520            standard = etree.SubElement(node, util.nspath_eval('gmd:metadataStandardName', self.namespaces))
521            etree.SubElement(standard, util.nspath_eval('gco:CharacterString', self.namespaces)).text = metadatastandardname
522
523            # metadata standard version
524            standardver = etree.SubElement(node, util.nspath_eval('gmd:metadataStandardVersion', self.namespaces))
525            etree.SubElement(standardver, util.nspath_eval('gco:CharacterString', self.namespaces)).text = metadatastandardversion
526
527        # title
528        val = util.getqattr(result, queryables['apiso:Title']['dbcol']) or ''
529        identification = etree.SubElement(node, util.nspath_eval('gmd:identificationInfo', self.namespaces))
530
531        if mtype == 'service':
532           restagname = 'srv:SV_ServiceIdentification'
533        else:
534           restagname = 'gmd:MD_DataIdentification'
535
536        resident = etree.SubElement(identification, util.nspath_eval(restagname, self.namespaces), id=idval)
537        tmp2 = etree.SubElement(resident, util.nspath_eval('gmd:citation', self.namespaces))
538        tmp3 = etree.SubElement(tmp2, util.nspath_eval('gmd:CI_Citation', self.namespaces))
539        tmp4 = etree.SubElement(tmp3, util.nspath_eval('gmd:title', self.namespaces))
540        etree.SubElement(tmp4, util.nspath_eval('gco:CharacterString', self.namespaces)).text = val
541
542        # creation date
543        val = util.getqattr(result, queryables['apiso:CreationDate']['dbcol'])
544        if val is not None:
545            tmp3.append(_write_date(val, 'creation', self.namespaces))
546        # publication date
547        val = util.getqattr(result, queryables['apiso:PublicationDate']['dbcol'])
548        if val is not None:
549            tmp3.append(_write_date(val, 'publication', self.namespaces))
550        # revision date
551        val = util.getqattr(result, queryables['apiso:RevisionDate']['dbcol'])
552        if val is not None:
553            tmp3.append(_write_date(val, 'revision', self.namespaces))
554
555        if esn in ['summary', 'full']:
556            # abstract
557            val = util.getqattr(result, queryables['apiso:Abstract']['dbcol']) or ''
558            tmp = etree.SubElement(resident, util.nspath_eval('gmd:abstract', self.namespaces))
559            etree.SubElement(tmp, util.nspath_eval('gco:CharacterString', self.namespaces)).text = val
560
561            # keywords
562            kw = util.getqattr(result, queryables['apiso:Subject']['dbcol'])
563            if kw is not None:
564                md_keywords = etree.SubElement(resident, util.nspath_eval('gmd:descriptiveKeywords', self.namespaces))
565                md_keywords.append(write_keywords(kw, self.namespaces))
566
567            # spatial resolution
568            val = util.getqattr(result, queryables['apiso:Denominator']['dbcol'])
569            if val:
570                tmp = etree.SubElement(resident, util.nspath_eval('gmd:spatialResolution', self.namespaces))
571                tmp2 = etree.SubElement(tmp, util.nspath_eval('gmd:MD_Resolution', self.namespaces))
572                tmp3 = etree.SubElement(tmp2, util.nspath_eval('gmd:equivalentScale', self.namespaces))
573                tmp4 = etree.SubElement(tmp3, util.nspath_eval('gmd:MD_RepresentativeFraction', self.namespaces))
574                tmp5 = etree.SubElement(tmp4, util.nspath_eval('gmd:denominator', self.namespaces))
575                etree.SubElement(tmp5, util.nspath_eval('gco:Integer', self.namespaces)).text = str(val)
576
577            # resource language
578            val = util.getqattr(result, queryables['apiso:ResourceLanguage']['dbcol'])
579            tmp = etree.SubElement(resident, util.nspath_eval('gmd:language', self.namespaces))
580            etree.SubElement(tmp, util.nspath_eval('gco:CharacterString', self.namespaces)).text = val
581
582            # topic category
583            val = util.getqattr(result, queryables['apiso:TopicCategory']['dbcol'])
584            if val:
585                for v in val.split(','):
586                    tmp = etree.SubElement(resident, util.nspath_eval('gmd:topicCategory', self.namespaces))
587                    etree.SubElement(tmp, util.nspath_eval('gmd:MD_TopicCategoryCode', self.namespaces)).text = val
588
589        # bbox extent
590        val = util.getqattr(result, queryables['apiso:BoundingBox']['dbcol'])
591        bboxel = write_extent(val, self.namespaces)
592        if bboxel is not None and mtype != 'service':
593            resident.append(bboxel)
594
595        # service identification
596
597        if mtype == 'service':
598            # service type
599            # service type version
600            val = util.getqattr(result, queryables['apiso:ServiceType']['dbcol'])
601            val2 = util.getqattr(result, queryables['apiso:ServiceTypeVersion']['dbcol'])
602            if val is not None:
603                tmp = etree.SubElement(resident, util.nspath_eval('srv:serviceType', self.namespaces))
604                etree.SubElement(tmp, util.nspath_eval('gco:LocalName', self.namespaces)).text = val
605                tmp = etree.SubElement(resident, util.nspath_eval('srv:serviceTypeVersion', self.namespaces))
606                etree.SubElement(tmp, util.nspath_eval('gco:CharacterString', self.namespaces)).text = val2
607
608            kw = util.getqattr(result, queryables['apiso:Subject']['dbcol'])
609            if kw is not None:
610                srv_keywords = etree.SubElement(resident, util.nspath_eval('srv:keywords', self.namespaces))
611                srv_keywords.append(write_keywords(kw, self.namespaces))
612
613            if bboxel is not None:
614                bboxel.tag = util.nspath_eval('srv:extent', self.namespaces)
615                resident.append(bboxel)
616
617            val = util.getqattr(result, queryables['apiso:CouplingType']['dbcol'])
618            if val is not None:
619                couplingtype = etree.SubElement(resident, util.nspath_eval('srv:couplingType', self.namespaces))
620                etree.SubElement(couplingtype, util.nspath_eval('srv:SV_CouplingType', self.namespaces), codeListValue=val, codeList='%s#SV_CouplingType' % CODELIST).text = val
621
622            if esn in ['summary', 'full']:
623                # all service resources as coupled resources
624                coupledresources = util.getqattr(result, queryables['apiso:OperatesOn']['dbcol'])
625                operations = util.getqattr(result, queryables['apiso:Operation']['dbcol'])
626
627                if coupledresources:
628                    for val2 in coupledresources.split(','):
629                        coupledres = etree.SubElement(resident, util.nspath_eval('srv:coupledResource', self.namespaces))
630                        svcoupledres = etree.SubElement(coupledres, util.nspath_eval('srv:SV_CoupledResource', self.namespaces))
631                        opname = etree.SubElement(svcoupledres, util.nspath_eval('srv:operationName', self.namespaces))
632                        etree.SubElement(opname, util.nspath_eval('gco:CharacterString', self.namespaces)).text = _get_resource_opname(operations)
633                        sid = etree.SubElement(svcoupledres, util.nspath_eval('srv:identifier', self.namespaces))
634                        etree.SubElement(sid, util.nspath_eval('gco:CharacterString', self.namespaces)).text = val2
635
636                # service operations
637                if operations:
638                    for i in operations.split(','):
639                        oper = etree.SubElement(resident, util.nspath_eval('srv:containsOperations', self.namespaces))
640                        tmp = etree.SubElement(oper, util.nspath_eval('srv:SV_OperationMetadata', self.namespaces))
641
642                        tmp2 = etree.SubElement(tmp, util.nspath_eval('srv:operationName', self.namespaces))
643                        etree.SubElement(tmp2, util.nspath_eval('gco:CharacterString', self.namespaces)).text = i
644
645                        tmp3 = etree.SubElement(tmp, util.nspath_eval('srv:DCP', self.namespaces))
646                        etree.SubElement(tmp3, util.nspath_eval('srv:DCPList', self.namespaces), codeList='%s#DCPList' % CODELIST, codeListValue='HTTPGet').text = 'HTTPGet'
647
648                        tmp4 = etree.SubElement(tmp, util.nspath_eval('srv:DCP', self.namespaces))
649                        etree.SubElement(tmp4, util.nspath_eval('srv:DCPList', self.namespaces), codeList='%s#DCPList' % CODELIST, codeListValue='HTTPPost').text = 'HTTPPost'
650
651                        connectpoint = etree.SubElement(tmp, util.nspath_eval('srv:connectPoint', self.namespaces))
652                        onlineres = etree.SubElement(connectpoint, util.nspath_eval('gmd:CI_OnlineResource', self.namespaces))
653                        linkage = etree.SubElement(onlineres, util.nspath_eval('gmd:linkage', self.namespaces))
654                        etree.SubElement(linkage, util.nspath_eval('gmd:URL', self.namespaces)).text = util.getqattr(result, self.context.md_core_model['mappings']['pycsw:Source'])
655
656                # operates on resource(s)
657                if coupledresources:
658                    for i in coupledresources.split(','):
659                        operates_on = etree.SubElement(resident, util.nspath_eval('srv:operatesOn', self.namespaces), uuidref=i)
660                        operates_on.attrib[util.nspath_eval('xlink:href', self.namespaces)] = '%sservice=CSW&version=2.0.2&request=GetRecordById&outputschema=http://www.isotc211.org/2005/gmd&id=%s-%s' % (util.bind_url(self.url), idval, i)
661
662        rlinks = util.getqattr(result, self.context.md_core_model['mappings']['pycsw:Links'])
663
664        if rlinks:
665            distinfo = etree.SubElement(node, util.nspath_eval('gmd:distributionInfo', self.namespaces))
666            distinfo2 = etree.SubElement(distinfo, util.nspath_eval('gmd:MD_Distribution', self.namespaces))
667            transopts = etree.SubElement(distinfo2, util.nspath_eval('gmd:transferOptions', self.namespaces))
668            dtransopts = etree.SubElement(transopts, util.nspath_eval('gmd:MD_DigitalTransferOptions', self.namespaces))
669
670            for link in rlinks.split('^'):
671                linkset = link.split(',')
672                online = etree.SubElement(dtransopts, util.nspath_eval('gmd:onLine', self.namespaces))
673                online2 = etree.SubElement(online, util.nspath_eval('gmd:CI_OnlineResource', self.namespaces))
674
675                linkage = etree.SubElement(online2, util.nspath_eval('gmd:linkage', self.namespaces))
676                etree.SubElement(linkage, util.nspath_eval('gmd:URL', self.namespaces)).text = linkset[-1]
677
678                protocol = etree.SubElement(online2, util.nspath_eval('gmd:protocol', self.namespaces))
679                etree.SubElement(protocol, util.nspath_eval('gco:CharacterString', self.namespaces)).text = linkset[2]
680
681                name = etree.SubElement(online2, util.nspath_eval('gmd:name', self.namespaces))
682                etree.SubElement(name, util.nspath_eval('gco:CharacterString', self.namespaces)).text = linkset[0]
683
684                desc = etree.SubElement(online2, util.nspath_eval('gmd:description', self.namespaces))
685                etree.SubElement(desc, util.nspath_eval('gco:CharacterString', self.namespaces)).text = linkset[1]
686
687        return node
688
689def write_keywords(keywords, nsmap):
690    """generate gmd:MD_Keywords construct"""
691    md_keywords = etree.Element(util.nspath_eval('gmd:MD_Keywords', nsmap))
692    for kw in keywords.split(','):
693        keyword = etree.SubElement(md_keywords, util.nspath_eval('gmd:keyword', nsmap))
694        etree.SubElement(keyword, util.nspath_eval('gco:CharacterString', nsmap)).text = kw
695    return md_keywords
696
697def write_extent(bbox, nsmap):
698    ''' Generate BBOX extent '''
699
700    if bbox is not None:
701        try:
702            bbox2 = util.wkt2geom(bbox)
703        except:
704            return None
705        extent = etree.Element(util.nspath_eval('gmd:extent', nsmap))
706        ex_extent = etree.SubElement(extent, util.nspath_eval('gmd:EX_Extent', nsmap))
707        ge = etree.SubElement(ex_extent, util.nspath_eval('gmd:geographicElement', nsmap))
708        gbb = etree.SubElement(ge, util.nspath_eval('gmd:EX_GeographicBoundingBox', nsmap))
709        west = etree.SubElement(gbb, util.nspath_eval('gmd:westBoundLongitude', nsmap))
710        east = etree.SubElement(gbb, util.nspath_eval('gmd:eastBoundLongitude', nsmap))
711        south = etree.SubElement(gbb, util.nspath_eval('gmd:southBoundLatitude', nsmap))
712        north = etree.SubElement(gbb, util.nspath_eval('gmd:northBoundLatitude', nsmap))
713
714        etree.SubElement(west, util.nspath_eval('gco:Decimal', nsmap)).text = str(bbox2[0])
715        etree.SubElement(south, util.nspath_eval('gco:Decimal', nsmap)).text = str(bbox2[1])
716        etree.SubElement(east, util.nspath_eval('gco:Decimal', nsmap)).text = str(bbox2[2])
717        etree.SubElement(north, util.nspath_eval('gco:Decimal', nsmap)).text = str(bbox2[3])
718        return extent
719    return None
720
721def _write_date(dateval, datetypeval, nsmap):
722    date1 = etree.Element(util.nspath_eval('gmd:date', nsmap))
723    date2 = etree.SubElement(date1, util.nspath_eval('gmd:CI_Date', nsmap))
724    date3 = etree.SubElement(date2, util.nspath_eval('gmd:date', nsmap))
725    if dateval.find('T') != -1:
726        dateel = 'gco:DateTime'
727    else:
728        dateel = 'gco:Date'
729    etree.SubElement(date3, util.nspath_eval(dateel, nsmap)).text = dateval
730    datetype = etree.SubElement(date2, util.nspath_eval('gmd:dateType', nsmap))
731    datetype.append(_write_codelist_element('gmd:CI_DateTypeCode', datetypeval, nsmap))
732    return date1
733
734def _get_resource_opname(operations):
735    for op in operations.split(','):
736        if op in ['GetMap', 'GetFeature', 'GetCoverage', 'GetObservation']:
737            return op
738    return None
739
740def _write_codelist_element(codelist_element, codelist_value, nsmap):
741    namespace, codelist = codelist_element.split(':')
742
743    element = etree.Element(util.nspath_eval(codelist_element, nsmap),
744    codeSpace=CODESPACE, codeList='%s#%s' % (CODELIST, codelist),
745    codeListValue=codelist_value)
746
747    element.text = codelist_value
748
749    return element
750