• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

geopython / OWSLib / 11628884181

01 Nov 2024 11:49AM UTC coverage: 58.814% (-1.3%) from 60.156%
11628884181

Pull #548

github

web-flow
Merge 4b9b7cf1f into ae98c2039
Pull Request #548: Strip out redundant __new__ to enable object pickling

8364 of 14221 relevant lines covered (58.81%)

1.18 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

79.04
/owslib/iso3.py
1
# -*- coding: ISO-8859-15 -*-
2
# =============================================================================
3
# Copyright (c) 2023 CSIRO Australia
4
#
5
# Author : Vincent Fazio
6
#
7
# Contact email: vincent.fazio@csiro.au
8
# =============================================================================
9

10
# flake8: noqa: E501
11

12
""" ISO 19115 Part 3 XML metadata parser
13

14
    Parsing is initiated by passing in etree root Element to the 'MD_Metadata' constructor:
15

16
    from owslib.etree import etree
17
    from owslib.iso3 import MD_Metadata
18

19
    exml = etree.fromstring(xml_bytes)
20
    mdb = MD_Metadata(exml)
21
"""
22

23
from owslib.etree import etree
2✔
24
from owslib import util
2✔
25

26

27
# ISO 19115 Part 3 XML namespaces
28
NAMESPACES_V2 = {
2✔
29
        "mdb":"http://standards.iso.org/iso/19115/-3/mdb/2.0",
30
        "cat":"http://standards.iso.org/iso/19115/-3/cat/1.0",
31
        "gfc":"http://standards.iso.org/iso/19110/gfc/1.1",
32
        "cit":"http://standards.iso.org/iso/19115/-3/cit/2.0",
33
        "gcx":"http://standards.iso.org/iso/19115/-3/gcx/1.0",
34
        "gex":"http://standards.iso.org/iso/19115/-3/gex/1.0",
35
        "lan":"http://standards.iso.org/iso/19115/-3/lan/1.0",
36
        "srv":"http://standards.iso.org/iso/19115/-3/srv/2.1",
37
        "mas":"http://standards.iso.org/iso/19115/-3/mas/1.0",
38
        "mcc":"http://standards.iso.org/iso/19115/-3/mcc/1.0",
39
        "mco":"http://standards.iso.org/iso/19115/-3/mco/1.0",
40
        "mda":"http://standards.iso.org/iso/19115/-3/mda/1.0",
41
        "mds":"http://standards.iso.org/iso/19115/-3/mds/2.0",
42
        "mdt":"http://standards.iso.org/iso/19115/-3/mdt/2.0",
43
        "mex":"http://standards.iso.org/iso/19115/-3/mex/1.0",
44
        "mmi":"http://standards.iso.org/iso/19115/-3/mmi/1.0",
45
        "mpc":"http://standards.iso.org/iso/19115/-3/mpc/1.0",
46
        "mrc":"http://standards.iso.org/iso/19115/-3/mrc/2.0",
47
        "mrd":"http://standards.iso.org/iso/19115/-3/mrd/1.0",
48
        "mri":"http://standards.iso.org/iso/19115/-3/mri/1.0",
49
        "mrl":"http://standards.iso.org/iso/19115/-3/mrl/2.0",
50
        "mrs":"http://standards.iso.org/iso/19115/-3/mrs/1.0",
51
        "msr":"http://standards.iso.org/iso/19115/-3/msr/2.0",
52
        "mdq":"http://standards.iso.org/iso/19157/-2/mdq/1.0",
53
        "mac":"http://standards.iso.org/iso/19115/-3/mac/2.0",
54
        "gco":"http://standards.iso.org/iso/19115/-3/gco/1.0",
55
        "gml":"http://www.opengis.net/gml",
56
        "xlink":"http://www.w3.org/1999/xlink",
57
        "xsi":"http://www.w3.org/2001/XMLSchema-instance"
58
}
59

60
NAMESPACES_V1 =  {
2✔
61
        "xsi":"http://www.w3.org/2001/XMLSchema-instance",
62
        "cat":"http://standards.iso.org/iso/19115/-3/cat/1.0",
63
        "cit":"http://standards.iso.org/iso/19115/-3/cit/1.0",
64
        "gcx":"http://standards.iso.org/iso/19115/-3/gcx/1.0",
65
        "gex":"http://standards.iso.org/iso/19115/-3/gex/1.0",
66
        "gfc":"http://standards.iso.org/iso/19110/gfc/1.1",
67
        "lan":"http://standards.iso.org/iso/19115/-3/lan/1.0",
68
        "srv":"http://standards.iso.org/iso/19115/-3/srv/2.0",
69
        "mac":"http://standards.iso.org/iso/19115/-3/mac/1.0",
70
        "mas":"http://standards.iso.org/iso/19115/-3/mas/1.0",
71
        "mcc":"http://standards.iso.org/iso/19115/-3/mcc/1.0",
72
        "mco":"http://standards.iso.org/iso/19115/-3/mco/1.0",
73
        "mda":"http://standards.iso.org/iso/19115/-3/mda/1.0",
74
        "mdb":"http://standards.iso.org/iso/19115/-3/mdb/1.0",
75
        "mdt":"http://standards.iso.org/iso/19115/-3/mdt/1.0",
76
        "mex":"http://standards.iso.org/iso/19115/-3/mex/1.0",
77
        "mrl":"http://standards.iso.org/iso/19115/-3/mrl/1.0",
78
        "mds":"http://standards.iso.org/iso/19115/-3/mds/1.0",
79
        "mmi":"http://standards.iso.org/iso/19115/-3/mmi/1.0",
80
        "mpc":"http://standards.iso.org/iso/19115/-3/mpc/1.0",
81
        "mrc":"http://standards.iso.org/iso/19115/-3/mrc/1.0",
82
        "mrd":"http://standards.iso.org/iso/19115/-3/mrd/1.0",
83
        "mri":"http://standards.iso.org/iso/19115/-3/mri/1.0",
84
        "mrs":"http://standards.iso.org/iso/19115/-3/mrs/1.0",
85
        "msr":"http://standards.iso.org/iso/19115/-3/msr/1.0",
86
        "mdq":"http://standards.iso.org/iso/19157/-2/mdq/1.0",
87
        "dqc":"http://standards.iso.org/iso/19157/-2/dqc/1.0",
88
        "gco":"http://standards.iso.org/iso/19115/-3/gco/1.0",
89
        "gml":"http://www.opengis.net/gml/3.2",
90
        "xlink":"http://www.w3.org/1999/xlink"
91
}
92

93
class printable():
2✔
94
    """ A super class used to roughly pretty print class members
95

96
        Usage:
97

98
        mdb = MD_Metadata(exml)
99
        print(mdb.format_me())
100

101
    """
102

103
    def format_me(self, idx=0):
2✔
104
        """ Returns a formatted string version of class
105

106
        :param idx: optional indentation index, internal use only, used for 'printable' member classes
107
        :returns: string version of class and members
108
        """
109
        repr_str = "\n"
×
110
        for d in dir(self):
×
111
            if not d.startswith("__") and not callable(getattr(self,d)):
×
112
                if isinstance(getattr(self,d), (str, bytes)):
×
113
                    repr_str += "  " * idx + f"{self.__class__.__name__}:{d}='{getattr(self,d)[:100]}'\n"
×
114
                elif isinstance(getattr(self,d), list):
×
115
                    repr_str += "  " * idx + f"{self.__class__.__name__}:{d}=[\n"
×
116
                    for item in getattr(self,d):
×
117
                        if isinstance(item, printable):
×
118
                            repr_str += "  " * idx + f"  {item.format_me(idx+1)}"
×
119
                        elif item is not None:
×
120
                            repr_str += "  " * idx + f"  {item}\n"
×
121
                    repr_str += "  " * idx + "]\n"
×
122
                elif isinstance(getattr(self,d), printable):
×
123
                    repr_str += "  " * idx + f"{self.__class__.__name__}:{d}={getattr(self,d).format_me(idx+1)}"
×
124
        return repr_str
×
125

126

127
class MD_Metadata(printable):
2✔
128
    """ Process mdb:MD_Metadata
129
    """
130
    def __init__(self, md=None):
2✔
131
        """
132
        Parses XML root tree
133

134
        :param md: etree.Element root
135
        """
136
        self.namespaces = NAMESPACES_V2
2✔
137
        if md is None:
2✔
138
            self.md = None
2✔
139
            self.xml = None
2✔
140
            self.identifier = None
2✔
141
            self.parentidentifier = None
2✔
142
            self.language = None
2✔
143
            self.dataseturi = None
2✔
144
            self.languagecode = None
2✔
145
            self.datestamp = None
2✔
146
            self.charset = None
2✔
147
            self.hierarchy = None
2✔
148
            self.contact = []
2✔
149
            self.datetimestamp = None
2✔
150
            self.stdname = None
2✔
151
            self.stdver = None
2✔
152
            self.locales = []
2✔
153
            self.referencesystem = None
2✔
154
            self.identification = []
2✔
155
            self.contentinfo = []
2✔
156
            self.distribution = None
2✔
157
            self.dataquality = None
2✔
158
            self.acquisition = None
2✔
159
        else:
160
            self.md = md
2✔
161
            if hasattr(md, 'getroot'):  # standalone document
2✔
162
                self.xml = etree.tostring(md.getroot())
×
163
            else:  # part of a larger document
164
                self.xml = etree.tostring(md)
2✔
165

166
           # Test mdb version
167
            if md.find(util.nspath_eval('mdb:metadataIdentifier', NAMESPACES_V2)) is None and \
2✔
168
                    md.find(util.nspath_eval('mdb:metadataIdentifier', NAMESPACES_V1)) is not None:
169
                self.namespaces = NAMESPACES_V1
2✔
170

171
            val = md.find(util.nspath_eval('mdb:metadataIdentifier/mcc:MD_Identifier/mcc:code/gco:CharacterString', self.namespaces))
2✔
172
            self.identifier = util.testXMLValue(val)
2✔
173

174
            val = md.find(util.nspath_eval('mdb:parentMetadata/cit:CI_Citation/cit:identifier/mcc:MD_Identifier/mcc:code/gco:CharacterString', self.namespaces))
2✔
175
            self.parentidentifier = util.testXMLValue(val)
2✔
176

177
            val = md.find(util.nspath_eval('lan:language/gco:CharacterString', self.namespaces))
2✔
178
            self.language = util.testXMLValue(val)
2✔
179

180
            val = md.find(util.nspath_eval('mdb:identificationInfo/mri:MD_DataIdentification/mri:citation/cit:CI_Citation/cit:identifier/mcc:MD_Identifier/mcc:code/gco:CharacterString', self.namespaces))
2✔
181
            self.dataseturi = util.testXMLValue(val)
2✔
182

183
            val = md.find(util.nspath_eval('mdb:defaultLocale/lan:PT_Locale/lan:language/lan:LanguageCode', self.namespaces))
2✔
184
            self.languagecode = util.testXMLAttribute(val, 'codeListValue')
2✔
185

186
            val = md.find(util.nspath_eval('mdb:dateInfo/cit:CI_Date/cit:date/gco:DateTime', self.namespaces))
2✔
187
            self.datestamp = util.testXMLValue(val)
2✔
188

189
            val = md.find(
2✔
190
                util.nspath_eval('mdb:defaultLocale/lan:PT_Locale/lan:characterEncoding/lan:MD_CharacterSetCode', self.namespaces))
191
            self.charset = util.testXMLAttribute(val, 'codeListValue')
2✔
192

193
            val = md.find(
2✔
194
                util.nspath_eval('mdb:metadataScope/mdb:MD_MetadataScope/mdb:resourceScope/mcc:MD_ScopeCode', self.namespaces))
195
            self.hierarchy = util.testXMLAttribute(val, 'codeListValue')
2✔
196

197
            self.contact = []
2✔
198
            for i in md.findall(util.nspath_eval('mdb:contact/cit:CI_Responsibility', self.namespaces)):
2✔
199
                o = CI_Responsibility(self.namespaces, i)
2✔
200
                self.contact.append(o)
2✔
201

202
            self.datetimestamp = self.datestamp
2✔
203

204
            val = md.find(util.nspath_eval('mdb:metadataStandard/cit:CI_Citation/cit:title/gco:CharacterString', self.namespaces))
2✔
205
            self.stdname = util.testXMLValue(val)
2✔
206

207
            val = md.find(util.nspath_eval('mdb:metadataStandard/cit:CI_Citation/cit:edition/gco:CharacterString', self.namespaces))
2✔
208
            self.stdver = util.testXMLValue(val)
2✔
209

210
            self.locales = []
2✔
211
            for i in md.findall(util.nspath_eval('mdb:defaultLocale/lan:PT_Locale', self.namespaces)):
2✔
212
                self.locales.append(PT_Locale(self.namespaces, i))
2✔
213

214
            val = md.find(util.nspath_eval('mdb:referenceSystemInfo/mrs:MD_ReferenceSystem', self.namespaces))
2✔
215
            if val is not None:
2✔
216
                self.referencesystem = MD_ReferenceSystem(self.namespaces, val)
2✔
217
            else:
218
                self.referencesystem = None
2✔
219

220
            self.identification = []
2✔
221

222
            for idinfo in md.findall(util.nspath_eval('mdb:identificationInfo', self.namespaces)):
2✔
223
                if len(idinfo) > 0:
2✔
224
                    val = list(idinfo)[0]
2✔
225
                    tagval = util.xmltag_split(val.tag)
2✔
226
                    if tagval == 'MD_DataIdentification':
2✔
227
                        self.identification.append(MD_DataIdentification(self.namespaces, val, 'dataset'))
2✔
228
                    elif tagval == 'MD_ServiceIdentification':
2✔
229
                        self.identification.append(MD_DataIdentification(self.namespaces, val, 'service'))
×
230
                    elif tagval == 'SV_ServiceIdentification':
2✔
231
                        self.identification.append(SV_ServiceIdentification(self.namespaces, val))
2✔
232

233
            self.contentinfo = []
2✔
234
            for contentinfo in md.findall(
2✔
235
                    util.nspath_eval('mdb:contentInfo/mrc:MD_FeatureCatalogueDescription', self.namespaces)):
236
                self.contentinfo.append(MD_FeatureCatalogueDescription(self.namespaces, contentinfo))
2✔
237
            for contentinfo in md.findall(
2✔
238
                    util.nspath_eval('mdb:contentInfo/mrc:MD_ImageDescription', self.namespaces)):
239
                self.contentinfo.append(MD_ImageDescription(self.namespaces, contentinfo))
2✔
240
            for contentinfo in md.findall(
2✔
241
                    util.nspath_eval('mdb:contentInfo/mrc:MD_FeatureCatalogue/mrc:featureCatalogue/gfc:FC_FeatureCatalogue',
242
                                     self.namespaces)):
243
                self.contentinfo.append(FC_FeatureCatalogue(self.namespaces, contentinfo))
×
244

245
            val = md.find(util.nspath_eval('mdb:distributionInfo/mrd:MD_Distribution', self.namespaces))
2✔
246

247
            if val is not None:
2✔
248
                self.distribution = MD_Distribution(self.namespaces, val)
2✔
249
            else:
250
                self.distribution = None
×
251

252
            val = md.find(util.nspath_eval('mdb:dataQualityInfo/mdq:DQ_DataQuality', self.namespaces))
2✔
253
            if val is not None:
2✔
254
                self.dataquality = DQ_DataQuality(self.namespaces, val)
2✔
255
            else:
256
                self.dataquality = None
2✔
257

258
    @staticmethod
2✔
259
    def find_start(doc):
2✔
260
        """ Tests for valid ISO 19115 Part 3 XML and returns the starting tag
261

262
        :param doc: lxml Element object
263
        :returns: 'mdb:MD_Metadata' lxml Element object
264
        """
265
        mtags = doc.xpath("//mdb:MD_Metadata", namespaces=NAMESPACES_V2)
2✔
266
        if len(mtags) > 0:
2✔
267
            return mtags[0]
2✔
268
        mtags = doc.xpath("//mdb:MD_Metadata", namespaces=NAMESPACES_V1)
×
269
        if len(mtags) > 0:
×
270
            return mtags[0]
×
271
        return None
×
272

273
    @staticmethod
2✔
274
    def handles(outputschema):
2✔
275
        """ Returns True iff the outputschema is handled by this class
276

277
        :param outputschema: outputschema parameter string
278
        :returns: True iff the outputschema is handled by this class
279
        """
280
        return outputschema == NAMESPACES_V1['mdb'] or \
2✔
281
               outputschema == NAMESPACES_V2['mdb']
282

283
    @staticmethod
2✔
284
    def find_ids(elemtree):
2✔
285
        """ Finds identifer strings and outer 'mdb:MD_Metadata' Elements
286

287
        :param elemtree: lxml.ElementTree to search in
288
        :returns: a list of tuples (id string, 'mdb:MD_Metadata' lxml.Element)
289
        """
290
        for ns in [NAMESPACES_V2, NAMESPACES_V1]:
2✔
291
            elems = elemtree.findall('.//' + util.nspath_eval('mdb:MD_Metadata', ns))
2✔
292
            if len(elems) > 0:
2✔
293
                ret_list = []
2✔
294
                for i in elems:
2✔
295
                    val = i.find(util.nspath_eval('mdb:metadataIdentifier/mcc:MD_Identifier/mcc:code/gco:CharacterString', NAMESPACES_V2))
2✔
296
                    ret_list.append((i, util.testXMLValue(val)))
2✔
297
                return ret_list
2✔
298
        return []
×
299

300
    def get_all_contacts(self):
2✔
301
        """ Get all contacts in identification part of document
302

303
        :returns: list of CI_Responsibility contacts
304
        """
305
        contacts = []
2✔
306
        for ii in self.identification:
2✔
307
            for iic in ii.contact:
2✔
308
                contacts.append(iic)
2✔
309

310
            for ct in ['creator', 'publisher', 'contributor', 'funder']:
2✔
311
                iict = getattr(ii, ct)
2✔
312
                if iict:
2✔
313
                    contacts.append(iict)
×
314

315
        return list(filter(None, contacts))
2✔
316

317
    def get_default_locale(self):
2✔
318
        """ Get default lan:PT_Locale based on lan:language
319

320
        :returns: default PT_Locale instance or None if not found
321
        """
322

323
        for loc in self.locales:
×
324
            if loc.languagecode == self.language:
×
325
                return loc
×
326
        return None
×
327

328

329
class PT_Locale(printable):
2✔
330
    """ Process PT_Locale
331
    """
332

333
    def __init__(self, namespaces, md=None):
2✔
334
        """
335
        Parses PT_Locale XML subtree
336

337
        :param namespaces: dict of XML namespaces, key is namespace, val is path
338
        :param md: PT_Locale etree.Element
339
        """
340
        self.namespaces = namespaces
2✔
341

342
        self.id = None
2✔
343
        self.languagecode = None
2✔
344
        self.charset = None
2✔
345

346
        if md is not None:
2✔
347
            try:
2✔
348
                self.id = md.attrib.get('id')
2✔
349
            except AttributeError:
×
350
                pass
×
351

352
            try:
2✔
353
                self.languagecode = md.find(
2✔
354
                    util.nspath_eval('lan:language/lan:LanguageCode', self.namespaces)).attrib.get('codeListValue')
355
            except AttributeError:
×
356
                pass
×
357

358
            try:
2✔
359
                self.charset = md.find(
2✔
360
                    util.nspath_eval('lan:characterEncoding/lan:MD_CharacterSetCode', self.namespaces)).attrib.get(
361
                    'codeListValue')
362
            except AttributeError:
×
363
                pass
×
364

365

366
class CI_Date(printable):
2✔
367
    """ Process CI_Date
368
    """
369
    def __init__(self, namespaces, md=None):
2✔
370
        """
371
        Parses CI_Date XML subtree
372

373
        :param namespaces: dict of XML namespaces, key is namespace, val is path
374
        :param md: CI_Date etree.Element
375
        """
376
        self.namespaces = namespaces
2✔
377
        if md is None:
2✔
378
            self.date = None
2✔
379
            self.type = None
2✔
380
        else:
381
            val = md.find(util.nspath_eval('cit:date/gco:Date', self.namespaces))
2✔
382
            if val is not None:
2✔
383
                self.date = util.testXMLValue(val)
2✔
384
            else:
385
                val = md.find(util.nspath_eval('cit:date/gco:DateTime', self.namespaces))
2✔
386
                if val is not None:
2✔
387
                    self.date = util.testXMLValue(val)
2✔
388
                else:
389
                    self.date = None
×
390

391
            val = md.find(util.nspath_eval('cit:dateType/cit:CI_DateTypeCode', self.namespaces))
2✔
392
            self.type = _testCodeListValue(val)
2✔
393

394

395
class CI_Responsibility(printable):
2✔
396
    """ Process CI_Responsibility
397
    """
398
    def __init__(self, namespaces, md=None):
2✔
399
        """
400
        Parses CI_Responsibility XML subtree
401

402
        :param namespaces: dict of XML namespaces, key is namespace, val is path
403
        :param md: CI_Responsibility etree.Element
404
        """
405
        self.namespaces = namespaces
2✔
406
        self.phone = None
2✔
407
        self.fax = None
2✔
408
        if md is None:
2✔
409
            self.name = None
2✔
410
            self.organization = None
2✔
411
            self.position = None
2✔
412
            self.address = None
2✔
413
            self.city = None
2✔
414
            self.region = None
2✔
415
            self.postcode = None
2✔
416
            self.country = None
2✔
417
            self.email = None
2✔
418
            self.onlineresource = None
2✔
419
            self.role = None
2✔
420
        else:
421
            # Individual name
422
            val = md.find(util.nspath_eval('cit:party/cit:CI_Individual/cit:name/gco:CharacterString', self.namespaces))
2✔
423
            self.name = util.testXMLValue(val)
2✔
424

425
            # Individual within organisation name
426
            if self.name is None:
2✔
427
                val = md.find(util.nspath_eval('cit:party/cit:CI_Organisation/cit:individual/cit:CI_Individual/cit:name/gco:CharacterString', self.namespaces))
2✔
428
                self.name = util.testXMLValue(val)
2✔
429

430
            # Organisation name
431
            val = md.find(util.nspath_eval('cit:party/cit:CI_Organisation/cit:name/gco:CharacterString', self.namespaces))
2✔
432
            self.organization = util.testXMLValue(val)
2✔
433

434
            # Individual within organisation position
435
            val = md.find(util.nspath_eval('cit:party/cit:CI_Organisation/cit:individual/cit:CI_Individual/cit:positionName/gco:CharacterString', self.namespaces))
2✔
436
            self.position = util.testXMLValue(val)
2✔
437

438
            # Organisation telephone
439
            val_list = md.xpath('cit:party/cit:CI_Organisation/cit:contactInfo/cit:CI_Contact/cit:phone/cit:CI_Telephone[cit:numberType/cit:CI_TelephoneTypeCode/@codeListValue="voice"]/cit:number/gco:CharacterString', namespaces=self.namespaces)
2✔
440
            if len(val_list) > 0:
2✔
441
                self.phone = util.testXMLValue(val_list[0])
2✔
442

443
            # Facsimile (Telephone and fax are differentiated by telephone type codes)
444
            val_list = md.xpath('cit:party/cit:CI_Organisation/cit:contactInfo/cit:CI_Contact/cit:phone/cit:CI_Telephone[cit:numberType/cit:CI_TelephoneTypeCode/@codeListValue="facsimile"]/cit:number/gco:CharacterString', namespaces=self.namespaces)
2✔
445
            if len(val_list) > 0:
2✔
446
                self.fax = util.testXMLValue(val_list[0])
2✔
447

448
            # Organisation address
449
            val = md.find(util.nspath_eval(
2✔
450
                'cit:party/cit:CI_Organisation/cit:contactInfo/cit:CI_Contact/cit:address/cit:CI_Address/cit:deliveryPoint/gco:CharacterString',
451
                self.namespaces))
452
            self.address = util.testXMLValue(val)
2✔
453

454
            val = md.find(util.nspath_eval(
2✔
455
                'cit:party/cit:CI_Organisation/cit:contactInfo/cit:CI_Contact/cit:address/cit:CI_Address/cit:city/gco:CharacterString', self.namespaces))
456
            self.city = util.testXMLValue(val)
2✔
457

458
            val = md.find(util.nspath_eval(
2✔
459
                'cit:party/cit:CI_Organisation/cit:contactInfo/cit:CI_Contact/cit:address/cit:CI_Address/cit:administrativeArea/gco:CharacterString',
460
                self.namespaces))
461
            self.region = util.testXMLValue(val)
2✔
462

463
            val = md.find(util.nspath_eval(
2✔
464
                'cit:party/cit:CI_Organisation/cit:contactInfo/cit:CI_Contact/cit:address/cit:CI_Address/cit:postalCode/gco:CharacterString',
465
                self.namespaces))
466
            self.postcode = util.testXMLValue(val)
2✔
467

468
            val = md.find(util.nspath_eval(
2✔
469
                'cit:party/cit:CI_Organisation/cit:contactInfo/cit:CI_Contact/cit:address/cit:CI_Address/cit:country/gco:CharacterString',
470
                self.namespaces))
471
            self.country = util.testXMLValue(val)
2✔
472

473
            # Organisation email
474
            val = md.find(util.nspath_eval(
2✔
475
                'cit:party/cit:CI_Organisation/cit:contactInfo/cit:CI_Contact/cit:address/cit:CI_Address/cit:electronicMailAddress/gco:CharacterString',
476
                self.namespaces))
477
            self.email = util.testXMLValue(val)
2✔
478

479
            # Organisation online resources
480
            val = md.find(util.nspath_eval(
2✔
481
                'cit:party/cit:CI_Organisation/cit:contactInfo/cit:CI_Contact/cit:onlineResource/cit:CI_OnlineResource', self.namespaces))
482
            if val is not None:
2✔
483
                self.onlineresource = CI_OnlineResource(self.namespaces, val)
2✔
484
            else:
485
                self.onlineresource = None
2✔
486
            val = md.find(util.nspath_eval('cit:role/cit:CI_RoleCode', self.namespaces))
2✔
487
            self.role = _testCodeListValue(val)
2✔
488

489

490
class Keyword(printable):
2✔
491
    """ Class for complex keywords, with labels and URLs
492
    """
493
    def __init__(self, namespaces, kw=None):
2✔
494
        """
495
        Parses keyword Element
496

497
        :param namespaces: dict of XML namespaces, key is namespace, val is path
498
        :param kw: keyword 'gco:CharacterString' or 'gcx:Anchor' etree.Element
499
        """
500
        self.namespaces = namespaces
2✔
501
        if kw is None:
2✔
502
            self.name = None
2✔
503
            self.url = None
2✔
504
        else:
505
            self.name = util.testXMLValue(kw)
2✔
506
            self.url = kw.attrib.get(util.nspath_eval('xlink:href', self.namespaces))
2✔
507

508

509
class MD_Keywords(printable):
2✔
510
    """
511
    Class for the metadata MD_Keywords element
512
    """
513
    def __init__(self, namespaces, md=None):
2✔
514
        """
515
        Parses keyword Element
516

517
        :param namespaces: dict of XML namespaces, key is namespace, val is path
518
        :param md: keyword etree.Element
519
        """
520
        self.namespaces = namespaces
2✔
521
        self.thesaurus = None
2✔
522
        self.keywords = []
2✔
523
        self.type = None
2✔
524
        self.kwdtype_codeList = 'http://standards.iso.org/iso/19115/-3/resources/Codelist/gmxCodelists.xml#MD_KeywordTypeCode'
2✔
525

526
        if md is not None:
2✔
527
            val = md.findall(util.nspath_eval('mri:keyword/gco:CharacterString', self.namespaces))
2✔
528
            if len(val) == 0:
2✔
529
                val = md.findall(util.nspath_eval('mri:keyword/gcx:Anchor', self.namespaces))
2✔
530
            for word in val:
2✔
531
                self.keywords.append(Keyword(self.namespaces, word))
2✔
532

533
            val = md.find(util.nspath_eval('mri:type/mri:MD_KeywordTypeCode', self.namespaces))
2✔
534
            self.type = util.testXMLAttribute(val, 'codeListValue')
2✔
535

536
            cit = md.find(util.nspath_eval('mri:thesaurusName/cit:CI_Citation', self.namespaces))
2✔
537
            if cit is not None:
2✔
538
                self.thesaurus = {}
2✔
539

540
                title = cit.find(util.nspath_eval('cit:title/gco:CharacterString', self.namespaces))
2✔
541
                self.thesaurus['title'] = util.testXMLValue(title)
2✔
542
                self.thesaurus['url'] = None
2✔
543

544
                if self.thesaurus['title'] is None:  # try gmx:Anchor
2✔
545
                    t = cit.find(util.nspath_eval('cit:title/gcx:Anchor', self.namespaces))
2✔
546
                    if t is not None:
2✔
547
                        self.thesaurus['title'] = util.testXMLValue(t)
2✔
548
                        self.thesaurus['url'] = t.attrib.get(util.nspath_eval('xlink:href', self.namespaces))
2✔
549

550
                date_ = cit.find(util.nspath_eval('cit:date/cit:CI_Date/cit:date/gco:Date', self.namespaces))
2✔
551
                self.thesaurus['date'] = util.testXMLValue(date_)
2✔
552

553
                datetype = cit.find(
2✔
554
                    util.nspath_eval('cit:date/cit:CI_Date/cit:dateType/cit:CI_DateTypeCode', self.namespaces))
555
                self.thesaurus['datetype'] = util.testXMLAttribute(datetype, 'codeListValue')
2✔
556

557
class MD_DataIdentification(printable):
2✔
558
    """ Process MD_DataIdentification
559
    """
560
    def __init__(self, namespaces, md=None, identtype=None):
2✔
561
        """
562
        Parses MD_DataIdentification XML subtree
563

564
        :param namespaces: dict of XML namespaces, key is namespace, val is path
565
        :param md: MD_DataIdentification etree.Element
566
        :param identtype: identitication type e.g. 'dataset' if MD_DataIdentification,
567
                                                   'service' if MD_ServiceIdentification
568
        """
569
        self.namespaces = namespaces
2✔
570
        self.aggregationinfo = None
2✔
571
        self.bbox = None
2✔
572
        self.temporalextent_start = None
2✔
573
        self.temporalextent_end = None
2✔
574
        self.extent = None
2✔
575
        if md is None:
2✔
576
            self.identtype = None
2✔
577
            self.title = None
2✔
578
            self.alternatetitle = None
2✔
579
            self.uricode = []
2✔
580
            self.uricodespace = []
2✔
581
            self.date = []
2✔
582
            self.datetype = []
2✔
583
            self.uselimitation = []
2✔
584
            self.uselimitation_url = []
2✔
585
            self.accessconstraints = []
2✔
586
            self.classification = []  # Left empty - no legal classification equivalent
2✔
587
            self.otherconstraints = []
2✔
588
            self.securityconstraints = []
2✔
589
            self.useconstraints = []
2✔
590
            self.denominators = []
2✔
591
            self.distance = []
2✔
592
            self.uom = []
2✔
593
            self.resourcelanguage = []
2✔
594
            self.resourcelanguagecode = []
2✔
595
            self.creator = []
2✔
596
            self.publisher = []
2✔
597
            self.funder = []
2✔
598
            self.contributor = []
2✔
599
            self.edition = None
2✔
600
            self.abstract = None
2✔
601
            self.abstract_url = None
2✔
602
            self.purpose = None
2✔
603
            self.status = None
2✔
604
            self.graphicoverview = []
2✔
605
            self.contact = []
2✔
606
            self.keywords = []
2✔
607
            self.topiccategory = []
2✔
608
            self.supplementalinformation = None
2✔
609
            self.spatialrepresentationtype = []
2✔
610
        else:
611
            # Title
612
            self.identtype = identtype
2✔
613
            val = md.find(util.nspath_eval(
2✔
614
                'mri:citation/cit:CI_Citation/cit:title/gco:CharacterString', self.namespaces))
615
            self.title = util.testXMLValue(val)
2✔
616

617
            val = md.find(util.nspath_eval(
2✔
618
                'mri:citation/cit:CI_Citation/cit:alternateTitle/gco:CharacterString', self.namespaces))
619
            self.alternatetitle = util.testXMLValue(val)
2✔
620

621
            # Identifier
622
            self.uricode = []
2✔
623
            for end_tag in ['gco:CharacterString', 'gcx:Anchor']:
2✔
624
                _values = md.findall(util.nspath_eval(
2✔
625
                    f"mri:citation/cit:CI_Citation/cit:identifier/mcc:MD_Identifier/mcc:code/{end_tag}",
626
                    self.namespaces))
627
                for i in _values:
2✔
628
                    val = util.testXMLValue(i)
2✔
629
                    if val is not None:
2✔
630
                        self.uricode.append(val)
2✔
631

632
            self.uricodespace = []
2✔
633
            for i in md.findall(util.nspath_eval(
2✔
634
                    'mri:citation/cit:CI_Citation/cit:identifier/mcc:MD_Identifier/mcc:codeSpace/gco:CharacterString',
635
                    self.namespaces)):
636
                val = util.testXMLValue(i)
2✔
637
                if val is not None:
2✔
638
                    self.uricodespace.append(val)
2✔
639

640
            # Date
641
            self.date = []
2✔
642
            self.datetype = []
2✔
643

644
            for i in md.findall(util.nspath_eval('mri:citation/cit:CI_Citation/cit:date/cit:CI_Date', self.namespaces)):
2✔
645
                self.date.append(CI_Date(self.namespaces, i))
2✔
646

647
            # Use Limitation
648
            self.uselimitation = []
2✔
649
            self.uselimitation_url = []
2✔
650
            uselimit_values = md.findall(util.nspath_eval(
2✔
651
                'mri:resourceConstraints/mco:MD_LegalConstraints/mco:useLimitation/gco:CharacterString', self.namespaces))
652
            for i in uselimit_values:
2✔
653
                val = util.testXMLValue(i)
2✔
654
                if val is not None:
2✔
655
                    self.uselimitation.append(val)
2✔
656

657
            # Access constraints
658
            self.accessconstraints = []
2✔
659
            for i in md.findall(util.nspath_eval(
2✔
660
                    'mri:resourceConstraints/mco:MD_LegalConstraints/mco:accessConstraints/mco:MD_RestrictionCode',
661
                    self.namespaces)):
662
                val = _testCodeListValue(i)
2✔
663
                if val is not None:
2✔
664
                    self.accessconstraints.append(val)
2✔
665

666
            # Classification
667
            self.classification = [] # Left empty - no legal classification equivalent
2✔
668

669
            # Other constraints
670
            self.otherconstraints = []
2✔
671
            for end_tag in ['gco:CharacterString', 'gcx:Anchor']:
2✔
672
                for i in md.findall(util.nspath_eval(
2✔
673
                        f"mri:resourceConstraints/mco:MD_LegalConstraints/mco:otherConstraints/{end_tag}",
674
                        self.namespaces)):
675
                    val = util.testXMLValue(i)
2✔
676
                    if val is not None:
2✔
677
                        self.otherconstraints.append(val)
2✔
678

679
            # Security constraints
680
            self.securityconstraints = []
2✔
681
            for i in md.findall(util.nspath_eval(
2✔
682
                    'mri:resourceConstraints/mco:MD_SecurityConstraints/mco:classification/mco:MD_ClassificationCode',
683
                    self.namespaces)):
684
                val = _testCodeListValue(i)
2✔
685
                if val is not None:
2✔
686
                    self.securityconstraints.append(val)
2✔
687

688
            # Use constraints
689
            self.useconstraints = []
2✔
690
            for i in md.findall(util.nspath_eval(
2✔
691
                    'mri:resourceConstraints/mco:MD_LegalConstraints/mco:useConstraints/mco:MD_RestrictionCode',
692
                    self.namespaces)):
693
                val = _testCodeListValue(i)
2✔
694
                if val is not None:
2✔
695
                    self.useconstraints.append(val)
2✔
696

697
            # Spatial resolution denominators
698
            self.denominators = []
2✔
699
            for i in md.findall(util.nspath_eval(
2✔
700
                    'mri:spatialResolution/mri:MD_Resolution/mri:equivalentScale/mri:MD_RepresentativeFraction/mri:denominator/gco:Integer',
701
                    self.namespaces)):
702
                val = util.testXMLValue(i)
2✔
703
                if val is not None:
2✔
704
                    self.denominators.append(val)
2✔
705

706
            # Spatial resolution distance and units of measure
707
            self.distance = []
2✔
708
            self.uom = []
2✔
709
            for i in md.findall(util.nspath_eval(
2✔
710
                    'mri:spatialResolution/mri:MD_Resolution/mri:distance/gco:Distance', self.namespaces)):
711
                val = util.testXMLValue(i)
2✔
712
                if val is not None:
2✔
713
                    self.distance.append(val)
2✔
714
                self.uom.append(i.get("uom"))
2✔
715

716
            # Language code
717
            self.resourcelanguagecode = []
2✔
718
            for i in md.findall(util.nspath_eval('mri:defaultLocale/lan:PT_Locale/lan:language/lan:LanguageCode', self.namespaces)):
2✔
719
                val = _testCodeListValue(i)
2✔
720
                if val is not None:
2✔
721
                    self.resourcelanguagecode.append(val)
2✔
722

723
            # Language
724
            self.resourcelanguage = []
2✔
725
            for i in md.findall(util.nspath_eval('mri:defaultLocale/lan:PT_Locale/lan:language/gco:CharacterString', self.namespaces)):
2✔
726
                val = util.testXMLValue(i)
×
727
                if val is not None:
×
728
                    self.resourcelanguage.append(val)
×
729

730
            self.creator = []
2✔
731
            self.publisher = []
2✔
732
            self.contributor = []
2✔
733
            self.funder = []
2✔
734
            # Extract roles from point of contact for resource
735
            for val in md.findall(util.nspath_eval('mri:pointOfContact/cit:CI_Responsibility', self.namespaces)):
2✔
736
                role = val.find(util.nspath_eval('cit:role/cit:CI_RoleCode', self.namespaces))
2✔
737
                if role is not None:
2✔
738
                    clv = _testCodeListValue(role)
2✔
739
                    rp = CI_Responsibility(self.namespaces, val)
2✔
740
                    # Creator
741
                    if clv in ['originator', 'principalInvestigator', 'author']:
2✔
742
                        self.creator.append(rp)
×
743
                    # Publisher
744
                    elif clv == 'publisher':
2✔
745
                        self.publisher.append(rp)
×
746
                    # Contributor
747
                    elif clv in ['collaborator', 'coAuthor', 'contributor', 'editor']:
2✔
748
                        self.contributor.append(rp)
×
749

750
            # Edition
751
            val = md.find(util.nspath_eval('cit:CI_Citation/cit:edition/gco:CharacterString', self.namespaces))
2✔
752
            self.edition = util.testXMLValue(val)
2✔
753

754
            # Extract roles from responsible party for resource
755
            for val in md.findall(util.nspath_eval('mri:citation/cit:CI_Citation/cit:citedResponsibleParty/cit:CI_Responsibility', self.namespaces)):
2✔
756
                role = val.find(util.nspath_eval('cit:role/cit:CI_RoleCode', self.namespaces))
2✔
757
                if role is not None:
2✔
758
                    clv = _testCodeListValue(role)
2✔
759
                    rp = CI_Responsibility(self.namespaces, val)
2✔
760
                    # Creator
761
                    if clv in ['originator', 'principalInvestigator', 'author']:
2✔
762
                        self.creator.append(rp)
2✔
763
                    # Publisher
764
                    elif clv == 'publisher':
2✔
765
                        self.publisher.append(rp)
2✔
766
                    # Contributor
767
                    elif clv in ['collaborator', 'coAuthor', 'contributor', 'processor', 'editor']:
2✔
768
                        self.contributor.append(rp)
2✔
769
                    # Funder
770
                    elif clv == 'funder':
2✔
771
                        self.funder.append(rp)
2✔
772

773
            # Abstract
774
            val = md.find(util.nspath_eval('mri:abstract/gco:CharacterString', self.namespaces))
2✔
775
            self.abstract = util.testXMLValue(val)
2✔
776

777
            val = md.find(util.nspath_eval('mri:abstract/gcx:Anchor', self.namespaces))
2✔
778

779
            self.abstract_url = None
2✔
780
            if val is not None:
2✔
781
                self.abstract = util.testXMLValue(val)
×
782
                self.abstract_url = val.attrib.get(util.nspath_eval('xlink:href', self.namespaces))
×
783

784
            # Purpose
785
            val = md.find(util.nspath_eval('mri:purpose/gco:CharacterString', self.namespaces))
2✔
786
            self.purpose = util.testXMLValue(val)
2✔
787

788
            # Status
789
            self.status = _testCodeListValue(md.find(util.nspath_eval('mri:status/mri:MD_ProgressCode', self.namespaces)))
2✔
790

791
            # Graphic overview
792
            self.graphicoverview = []
2✔
793
            for val in md.findall(util.nspath_eval(
2✔
794
                    'mri:graphicOverview/mcc:MD_BrowseGraphic/mcc:fileName/gco:CharacterString', self.namespaces)):
795
                if val is not None:
2✔
796
                    val2 = util.testXMLValue(val)
2✔
797
                    if val2 is not None:
2✔
798
                        self.graphicoverview.append(val2)
2✔
799

800
            # Point of Contact
801
            self.contact = []
2✔
802
            for i in md.findall(util.nspath_eval('mri:pointOfContact/cit:CI_Responsibility', self.namespaces)):
2✔
803
                o = CI_Responsibility(self.namespaces, i)
2✔
804
                self.contact.append(o)
2✔
805

806
            # Spatial repreentation type
807
            self.spatialrepresentationtype = []
2✔
808
            for val in md.findall(util.nspath_eval(
2✔
809
                    'mri:spatialRepresentationType/mcc:MD_SpatialRepresentationTypeCode', self.namespaces)):
810
                val = util.testXMLAttribute(val, 'codeListValue')
2✔
811
                if val:
2✔
812
                    self.spatialrepresentationtype.append(val)
2✔
813

814
            # Keywords
815
            self.keywords = []
2✔
816
            for mdkw in md.findall(util.nspath_eval('mri:descriptiveKeywords/mri:MD_Keywords', self.namespaces)):
2✔
817
                self.keywords.append(MD_Keywords(self.namespaces, mdkw))
2✔
818

819
            # Topic category
820
            self.topiccategory = []
2✔
821
            for i in md.findall(util.nspath_eval('mri:topicCategory/mri:MD_TopicCategoryCode', self.namespaces)):
2✔
822
                val = util.testXMLValue(i)
2✔
823
                if val is not None:
2✔
824
                    self.topiccategory.append(val)
2✔
825

826
            # Supplamental information
827
            val = md.find(util.nspath_eval('mri:supplementalInformation/gco:CharacterString', self.namespaces))
2✔
828
            self.supplementalinformation = util.testXMLValue(val)
2✔
829

830
            # There may be multiple geographicElement, create an extent
831
            # from the one containing either an EX_GeographicBoundingBox or EX_BoundingPolygon.
832
            # The schema also specifies an EX_GeographicDescription. This is not implemented yet.
833
            val = None
2✔
834
            val2 = None
2✔
835
            val3 = None
2✔
836
            extents = md.findall(util.nspath_eval('mri:extent', self.namespaces))
2✔
837
            for extent in extents:
2✔
838
                # Parse bounding box and vertical extents
839
                if val is None:
2✔
840
                    for e in extent.findall(util.nspath_eval('gex:EX_Extent/gex:geographicElement', self.namespaces)):
2✔
841
                        if e.find(util.nspath_eval('gex:EX_GeographicBoundingBox', self.namespaces)) is not None or \
2✔
842
                                e.find(util.nspath_eval('gex:EX_BoundingPolygon', self.namespaces)) is not None:
843
                            val = e
2✔
844
                            break
2✔
845
                    vert_elem = extent.find(util.nspath_eval('gex:EX_Extent/gex:verticalElement', self.namespaces))
2✔
846
                    self.extent = EX_Extent(self.namespaces, val, vert_elem)
2✔
847
                    self.bbox = self.extent.boundingBox  # for backwards compatibility
2✔
848

849
                # Parse temporal extent begin
850
                if val2 is None:
2✔
851
                    val2 = extent.find(util.nspath_eval(
2✔
852
                        'gex:EX_Extent/gex:temporalElement/gex:EX_TemporalExtent/gex:extent/gml:TimePeriod/gml:beginPosition',
853
                        self.namespaces))
854
                    self.temporalextent_start = util.testXMLValue(val2)
2✔
855

856
                # Parse temporal extent end
857
                if val3 is None:
2✔
858
                    val3 = extent.find(util.nspath_eval(
2✔
859
                        'gex:EX_Extent/gex:temporalElement/gex:EX_TemporalExtent/gex:extent/gml:TimePeriod/gml:endPosition',
860
                        self.namespaces))
861
                    self.temporalextent_end = util.testXMLValue(val3)
2✔
862

863

864
class MD_Distributor(printable):
2✔
865
    """ Process MD_Distributor
866
    """
867
    def __init__(self, namespaces, md=None):
2✔
868
        """
869
        Parses MD_Distributor XML subtree
870

871
        :param namespaces: dict of XML namespaces, key is namespace, val is path
872
        :param md: MD_Distributor etree.Element
873
        """
874
        self.namespaces = namespaces
2✔
875
        if md is None:
2✔
876
            self.contact = None
2✔
877
            self.online = []
2✔
878
        else:
879
            self.contact = None
2✔
880
            val = md.find(util.nspath_eval(
2✔
881
                'mrd:MD_Distributor/mrd:distributorContact/cit:CI_Responsibility', self.namespaces))
882
            if val is not None:
2✔
883
                self.contact = CI_Responsibility(self.namespaces, val)
2✔
884

885
            self.online = []
2✔
886

887
            for ol in md.findall(util.nspath_eval(
2✔
888
                    'mrd:MD_Distributor/mrd:distributorTransferOptions/mrd:MD_DigitalTransferOptions/mrd:onLine/cit:CI_OnlineResource',
889
                    self.namespaces)):
890
                self.online.append(CI_OnlineResource(self.namespaces, ol))
2✔
891

892

893
class MD_Distribution(printable):
2✔
894
    """ Process MD_Distribution
895
    """
896
    def __init__(self, namespaces, md=None):
2✔
897
        """
898
        Parses MD_Distribution XML subtree
899

900
        :param namespaces: dict of XML namespaces, key is namespace, val is path
901
        :param md: MD_Distribution etree.Element
902
        """
903
        self.namespaces = namespaces
2✔
904
        if md is None:
2✔
905
            self.format = None
2✔
906
            self.version = None
2✔
907
            self.distributor = []
2✔
908
            self.online = []
2✔
909
        else:
910
            val = md.find(util.nspath_eval(
2✔
911
                'mrd:distributionFormat/mrd:MD_Format/mrd:formatSpecificationCitation/cit:CI_Citation/cit:title/gco:CharacterString', self.namespaces))
912
            self.format = util.testXMLValue(val)
2✔
913

914
            val = md.find(util.nspath_eval(
2✔
915
                'mrd:distributionFormat/mrd:MD_Format/mrd:formatSpecificationCitation/cit:CI_Citation/cit:edition/gco:CharacterString', self.namespaces))
916
            self.version = util.testXMLValue(val)
2✔
917

918
            self.distributor = []
2✔
919
            for dist in md.findall(util.nspath_eval('mrd:distributor', self.namespaces)):
2✔
920
                self.distributor.append(MD_Distributor(self.namespaces, dist))
2✔
921

922
            self.online = []
2✔
923

924
            for ol in md.findall(util.nspath_eval(
2✔
925
                    'mrd:transferOptions/mrd:MD_DigitalTransferOptions/mrd:onLine/cit:CI_OnlineResource',
926
                    self.namespaces)):
927
                self.online.append(CI_OnlineResource(self.namespaces, ol))
2✔
928

929

930
class DQ_DataQuality(printable):
2✔
931
    """ Process DQ_DataQuality
932
    """
933
    def __init__(self, namespaces, md=None):
2✔
934
        """
935
        Parse a portion of DQ_DataQuality XML subtree only taking the first value found
936

937
        :param namespaces: dict of XML namespaces, key is namespace, val is path
938
        :param md: DQ_DataQuality etree.Element
939
        """
940
        self.namespaces = namespaces
2✔
941
        self.conformancetitle = []
2✔
942
        self.conformancedate = []
2✔
943
        self.conformancedatetype = []
2✔
944
        self.conformancedegree = []
2✔
945
        self.lineage = None
2✔
946
        self.lineage_url = None
2✔
947
        self.specificationtitle = None
2✔
948
        self.specificationdate = []
2✔
949
        if md is not None:
2✔
950
            
951
            for conftitle in md.xpath(
2✔
952
                    'mdq:report/mdq:DQ_DomainConsistency/mdq:result/mdq:DQ_ConformanceResult/mdq:specification/cit:CI_Citation/cit:title/gco:CharacterString',
953
                    namespaces=self.namespaces):
954
                self.conformancetitle.append(util.testXMLValue(conftitle))
2✔
955

956
            for confdate in md.xpath(
2✔
957
                    'mdq:report/mdq:DQ_DomainConsistency/mdq:result/mdq:DQ_ConformanceResult/mdq:specification/cit:CI_Citation/cit:date/cit:CI_Date/cit:date/gco:DateTime',
958
                    namespaces=self.namespaces):
959
                self.conformancedate.append(util.testXMLValue(confdate))
2✔
960

961
            for confdatetype in md.xpath(
2✔
962
                    'mdq:report/mdq:DQ_DomainConsistency/mdq:result/mdq:DQ_ConformanceResult/mdq:specification/cit:CI_Citation/cit:date/cit:CI_Date/cit:dateType/cit:CI_DateTypeCode',
963
                    namespaces=self.namespaces):
964
                self.conformancedatetype.append(util.testXMLValue(confdatetype))
2✔
965

966
            for confdegree in md.xpath(
2✔
967
                    'mdq:report/mdq:DQ_DomainConsistency/mdq:result/mdq:DQ_ConformanceResult/mdq:pass/gco:Boolean',
968
                    namespaces=self.namespaces):
969
                self.conformancedegree.append(util.testXMLValue(confdegree))
2✔
970

971
            lins = md.xpath(
2✔
972
                'mdq:lineage/mrl:LI_Lineage/mrl:statement/*[self::gco:CharacterString or self::gcx:Anchor]',
973
                namespaces=self.namespaces)
974
            if len(lins) > 0:
2✔
975
                self.lineage = util.testXMLValue(lins[0])
×
976
                self.lineage_url = lins[0].attrib.get(util.nspath_eval('xlink:href', self.namespaces))
×
977

978
            val = md.find(util.nspath_eval(
2✔
979
                'mdq:report/mdq:DQ_DomainConsistency/mdq:result/mdq:DQ_ConformanceResult/mdq:specification/cit:CI_Citation/cit:title/gco:CharacterString',
980
                self.namespaces))
981
            self.specificationtitle = util.testXMLValue(val)
2✔
982

983
            self.specificationdate = []
2✔
984
            for i in md.findall(util.nspath_eval(
2✔
985
                    'mdq:report/mdq:DQ_DomainConsistency/mdq:result/mdq:DQ_ConformanceResult/mdq:specification/cit:CI_Citation/cit:date/cit:CI_Date/cit:date/gco:DateTime',
986
                    self.namespaces)):
987
                val = util.testXMLValue(i)
2✔
988
                if val is not None:
2✔
989
                    self.specificationdate.append(val)
2✔
990

991

992
class SV_ServiceIdentification(MD_DataIdentification, printable):
2✔
993
    """ Process SV_ServiceIdentification
994
    """
995
    def __init__(self, namespaces, md=None):
2✔
996
        """
997
        Parses SV_ServiceIdentification XML subtree
998

999
        :param namespaces: dict of XML namespaces, key is namespace, val is path
1000
        :param md: SV_ServiceIdentification etree.Element
1001
        """
1002
        super().__init__(namespaces, md, 'service')
2✔
1003
        self.namespaces = namespaces
2✔
1004

1005
        if md is None:
2✔
1006
            self.type = None
2✔
1007
            self.version = None
2✔
1008
            self.fees = None
2✔
1009
            self.couplingtype = None
2✔
1010
            self.operations = []
2✔
1011
            self.operateson = []
2✔
1012
        else:
1013
            val = md.xpath('srv:serviceType/*[self::gco:LocalName or self::gco:ScopedName]', namespaces=self.namespaces)
2✔
1014
            if len(val) > 0:
2✔
1015
                self.type = util.testXMLValue(val[0])
2✔
1016

1017
            val = md.find(util.nspath_eval('srv:serviceTypeVersion/gco:CharacterString', self.namespaces))
2✔
1018
            self.version = util.testXMLValue(val)
2✔
1019

1020
            val = md.find(util.nspath_eval(
2✔
1021
                'srv:accessProperties/mrd:MD_StandardOrderProcess/mrd:fees/gco:CharacterString', self.namespaces))
1022
            self.fees = util.testXMLValue(val)
2✔
1023

1024
            self.couplingtype = _testCodeListValue(md.find(util.nspath_eval(
2✔
1025
                'srv:couplingType/srv:SV_CouplingType', self.namespaces)))
1026

1027
            self.operations = []
2✔
1028

1029
            for i in md.findall(util.nspath_eval('srv:containsOperations', self.namespaces)):
2✔
1030
                tmp = {}
×
1031
                val = i.find(util.nspath_eval(
×
1032
                    'srv:SV_OperationMetadata/srv:operationName/gco:CharacterString', self.namespaces))
1033
                tmp['name'] = util.testXMLValue(val)
×
1034
                tmp['dcplist'] = []
×
1035
                for d in i.findall(util.nspath_eval('srv:SV_OperationMetadata/srv:distributedComputingPlatform', self.namespaces)):
×
1036
                    tmp2 = _testCodeListValue(d.find(util.nspath_eval('srv:DCPList', self.namespaces)))
×
1037
                    tmp['dcplist'].append(tmp2)
×
1038

1039
                tmp['connectpoint'] = []
×
1040

1041
                for d in i.findall(util.nspath_eval('srv:SV_OperationMetadata/srv:connectPoint', self.namespaces)):
×
1042
                    tmp3 = d.find(util.nspath_eval('cit:CI_OnlineResource', self.namespaces))
×
1043
                    tmp['connectpoint'].append(CI_OnlineResource(self.namespaces, tmp3))
×
1044
                self.operations.append(tmp)
×
1045

1046
            self.operateson = []
2✔
1047

1048
            for i in md.findall(util.nspath_eval('srv:operatesOn', self.namespaces)):
2✔
1049
                tmp = {}
2✔
1050
                tmp['uuidref'] = i.attrib.get('uuidref')
2✔
1051
                tmp['href'] = i.attrib.get(util.nspath_eval('xlink:href', self.namespaces))
2✔
1052
                tmp['title'] = i.attrib.get(util.nspath_eval('xlink:title', self.namespaces))
2✔
1053
                self.operateson.append(tmp)
2✔
1054

1055

1056
class CI_OnlineResource(printable):
2✔
1057
    """ Process CI_OnlineResource
1058
    """
1059
    def __init__(self, namespaces, md=None):
2✔
1060
        """
1061
        Parses CI_OnlineResource XML subtree
1062

1063
        :param namespaces: dict of XML namespaces, key is namespace, val is path
1064
        :param md: CI_OnlineResource etree.Element
1065
        """
1066
        self.namespaces = namespaces
2✔
1067
        if md is None:
2✔
1068
            self.url = None
2✔
1069
            self.protocol = None
2✔
1070
            self.name = None
2✔
1071
            self.description = None
2✔
1072
            self.function = None
2✔
1073
        else:
1074
            val = md.find(util.nspath_eval('cit:linkage/gco:CharacterString', self.namespaces))
2✔
1075
            self.url = util.testXMLValue(val)
2✔
1076

1077
            val = md.find(util.nspath_eval('cit:protocol/gco:CharacterString', self.namespaces))
2✔
1078
            self.protocol = util.testXMLValue(val)
2✔
1079

1080
            val = md.find(util.nspath_eval('cit:name/gco:CharacterString', self.namespaces))
2✔
1081
            self.name = util.testXMLValue(val)
2✔
1082

1083
            val = md.find(util.nspath_eval('cit:description/gco:CharacterString', self.namespaces))
2✔
1084
            self.description = util.testXMLValue(val)
2✔
1085

1086
            self.function = _testCodeListValue(md.find(util.nspath_eval(
2✔
1087
                'cit:function/cit:CI_OnLineFunctionCode', self.namespaces)))
1088

1089

1090
class EX_GeographicBoundingBox(printable):
2✔
1091
    """ Process gex:EX_GeographicBoundingBox
1092
    """
1093
    def __init__(self, namespaces, md=None):
2✔
1094
        """
1095
        Parses EX_GeographicBoundingBox XML subtree
1096

1097
        :param namespaces: dict of XML namespaces, key is namespace, val is path
1098
        :param md: EX_GeographicBoundingBox etree.Element
1099
        """
1100
        self.namespaces = namespaces
2✔
1101
        if md is None:
2✔
1102
            self.minx = None
2✔
1103
            self.maxx = None
2✔
1104
            self.miny = None
2✔
1105
            self.maxy = None
2✔
1106
        else:
1107
            val = md.find(util.nspath_eval('gex:westBoundLongitude/gco:Decimal', self.namespaces))
2✔
1108
            self.minx = util.testXMLValue(val)
2✔
1109
            val = md.find(util.nspath_eval('gex:eastBoundLongitude/gco:Decimal', self.namespaces))
2✔
1110
            self.maxx = util.testXMLValue(val)
2✔
1111
            val = md.find(util.nspath_eval('gex:southBoundLatitude/gco:Decimal', self.namespaces))
2✔
1112
            self.miny = util.testXMLValue(val)
2✔
1113
            val = md.find(util.nspath_eval('gex:northBoundLatitude/gco:Decimal', self.namespaces))
2✔
1114
            self.maxy = util.testXMLValue(val)
2✔
1115

1116

1117
class EX_Polygon(printable):
2✔
1118
    """ Process gml32:Polygon
1119
    """
1120
    def __init__(self, namespaces, md=None):
2✔
1121
        """
1122
        Parses EX_Polygon XML subtree
1123

1124
        :param namespaces: dict of XML namespaces, key is namespace, val is path
1125
        :param md: EX_Polygon etree.Element
1126
        """
1127
        self.namespaces = namespaces
2✔
1128

1129
        self.exterior_ring = None
2✔
1130
        self.interior_rings = []
2✔
1131

1132
        if md is not None:
2✔
1133
            try:
×
1134
                linear_ring = md.find(util.nspath_eval('gml32:Polygon/gml32:exterior/gml32:LinearRing', self.namespaces))
×
1135
                if linear_ring is not None:
×
1136
                    self.exterior_ring = self._coordinates_for_ring(linear_ring)
×
1137
            except KeyError:
×
1138
                pass
×
1139

1140
            try:
×
1141
                interior_ring_elements = md.findall(util.nspath_eval('gml32:Polygon/gml32:interior', self.namespaces))
×
1142
                self.interior_rings = []
×
1143
                if interior_ring_elements is not None:
×
1144
                    for iring_element in interior_ring_elements:
×
1145
                        try:
×
1146
                            linear_ring = iring_element.find(util.nspath_eval('gml32:LinearRing', self.namespaces))
×
1147
                            self.interior_rings.append(self._coordinates_for_ring(linear_ring))
×
1148
                        except KeyError:
×
1149
                            pass
×
1150
            except KeyError:
×
1151
                pass
×
1152

1153

1154
    def _coordinates_for_ring(self, linear_ring):
2✔
1155
        """ Get coordinates for gml coordinate ring
1156

1157
        :param linear_ring: etree.Element position list
1158
        :returns: coordinate list of float tuples
1159
        """
1160
        coordinates = []
×
1161
        positions = linear_ring.findall(util.nspath_eval('gml32:pos', self.namespaces))
×
1162
        for pos in positions:
×
1163
            tokens = pos.text.split()
×
1164
            coords = tuple([float(t) for t in tokens])
×
1165
            coordinates.append(coords)
×
1166
        return coordinates
×
1167

1168

1169
class EX_BoundingPolygon(printable):
2✔
1170
    """ Process EX_BoundingPolygon
1171
    """
1172
    def __init__(self, namespaces, md=None):
2✔
1173
        """
1174
        Parses EX_BoundingPolygon XML subtree
1175

1176
        :param namespaces: dict of XML namespaces, key is namespace, val is path
1177
        :param md: EX_BoundingPolygon etree.Element
1178
        """
1179
        self.namespaces = namespaces
2✔
1180
        if md is None:
2✔
1181
            self.is_extent = None
2✔
1182
            self.polygons = []
2✔
1183
        else:
1184
            val = md.find(util.nspath_eval('gex:extentTypeCode', self.namespaces))
×
1185
            self.is_extent = util.testXMLValue(val)
×
1186

1187
            md_polygons = md.findall(util.nspath_eval('gex:polygon', self.namespaces))
×
1188

1189
            self.polygons = []
×
1190
            for val in md_polygons:
×
1191
                self.polygons.append(EX_Polygon(self.namespaces, val))
×
1192

1193

1194
class EX_Extent(printable):
2✔
1195
    """ Process EX_Extent
1196
    """
1197
    def __init__(self, namespaces, md=None, vert_elem=None):
2✔
1198
        """
1199
        Parses EX_Extent XML subtree
1200

1201
        :param namespaces: dict of XML namespaces, key is namespace, val is path
1202
        :param md: EX_Extent etree.Element
1203
        :param vert_elem: vertical extent 'gex:verticalElement' etree.Element
1204
        """
1205
        self.namespaces = namespaces
2✔
1206
        self.boundingBox = None
2✔
1207
        self.boundingPolygon = None
2✔
1208
        self.description_code = None
2✔
1209
        self.vertExtMin = None
2✔
1210
        self.vertExtMax = None
2✔
1211
        if md is not None:
2✔
1212
            # Parse bounding box
1213
            bboxElement = md.find(util.nspath_eval('gex:EX_GeographicBoundingBox', self.namespaces))
2✔
1214
            if bboxElement is not None:
2✔
1215
                self.boundingBox = EX_GeographicBoundingBox(self.namespaces, bboxElement)
2✔
1216

1217
            polygonElement = md.find(util.nspath_eval('gex:EX_BoundingPolygon', self.namespaces))
2✔
1218
            if polygonElement is not None:
2✔
1219
                self.boundingPolygon = EX_BoundingPolygon(self.namespaces, polygonElement)
×
1220

1221
            code = md.find(util.nspath_eval(
2✔
1222
                'gex:EX_GeographicDescription/gex:geographicIdentifier/mcc:MD_Identifier/mcc:code/gco:CharacterString',
1223
                self.namespaces))
1224
            self.description_code = util.testXMLValue(code)
2✔
1225

1226
        # Parse vertical extent
1227
        if vert_elem is not None:
2✔
1228
            # Get vertical extent max
1229
            vertext_max = vert_elem.find(util.nspath_eval(
2✔
1230
                'gex:EX_VerticalExtent/gex:maximumValue/gco:Real',
1231
                self.namespaces))
1232
            self.vertExtMax = util.testXMLValue(vertext_max)
2✔
1233

1234
            # Get vertical extent min
1235
            vertext_min = vert_elem.find(util.nspath_eval(
2✔
1236
                'gex:EX_VerticalExtent/gex:minimumValue/gco:Real',
1237
                self.namespaces))
1238
            self.vertExtMin = util.testXMLValue(vertext_min)
2✔
1239

1240

1241
class MD_ReferenceSystem(printable):
2✔
1242
    """ Process MD_ReferenceSystem
1243
    """
1244
    def __init__(self, namespaces, md=None):
2✔
1245
        """
1246
        Parses MD_ReferenceSystem XML subtree
1247

1248
        :param namespaces: dict of XML namespaces, key is namespace, val is path
1249
        :param md: MD_ReferenceSystem etree.Element
1250
        """
1251
        self.namespaces = namespaces
2✔
1252
        if md is None:
2✔
1253
            self.code = None
2✔
1254
            self.codeSpace = None
2✔
1255
            self.version = None
2✔
1256
        else:
1257
            val = md.find(util.nspath_eval(
2✔
1258
                'mrs:referenceSystemIdentifier/mcc:MD_Identifier/mcc:code/gco:CharacterString', self.namespaces))
1259
            if val is not None:
2✔
1260
                self.code = util.testXMLValue(val)
2✔
1261
            else:
1262
                val = md.find(util.nspath_eval(
2✔
1263
                'mrs:referenceSystemIdentifier/mcc:MD_Identifier/mcc:code/gcx:Anchor', self.namespaces))
1264
                if val is not None:
2✔
1265
                    self.code = util.testXMLValue(val)
2✔
1266
                else:
1267
                    self.code = None
×
1268

1269
            val = md.find(util.nspath_eval(
2✔
1270
                'mrs:referenceSystemIdentifier/mcc:MD_Identifier/mcc:codeSpace/gco:CharacterString', self.namespaces))
1271
            if val is not None:
2✔
1272
                self.codeSpace = util.testXMLValue(val)
2✔
1273
            else:
1274
                self.codeSpace = None
2✔
1275

1276
            val = md.find(util.nspath_eval(
2✔
1277
                'mrs:referenceSystemIdentifier/mcc:MD_Identifier/mcc:version/gco:CharacterString', self.namespaces))
1278
            if val is not None:
2✔
1279
                self.version = util.testXMLValue(val)
2✔
1280
            else:
1281
                self.version = None
2✔
1282

1283

1284
def _testCodeListValue(elpath):
2✔
1285
    """ Get codeListValue attribute, else get text content
1286

1287
    :param elpath: Element path
1288
    :returns: 'codeListValue' attribute of Element or text value or None if elpath is None
1289
    """
1290
    if elpath is not None:  # try to get @codeListValue
2✔
1291
        val = util.testXMLValue(elpath.attrib.get('codeListValue'), True)
2✔
1292
        if val is not None:
2✔
1293
            return val
2✔
1294
        # see if there is element text
1295
        return util.testXMLValue(elpath)
×
1296

1297
    return None
2✔
1298

1299

1300
class MD_FeatureCatalogueDescription(printable):
2✔
1301
    """Process mrc:MD_FeatureCatalogueDescription
1302
    """
1303
    def __init__(self, namespaces, fcd=None):
2✔
1304
        """
1305
        Parses MD_FeatureCatalogueDescription XML subtree
1306

1307
        :param namespaces: dict of XML namespaces, key is namespace, val is path
1308
        :param fcd: MD_FeatureCatalogueDescription etree.Element
1309
        """
1310
        self.namespaces = namespaces
2✔
1311
        self.featuretypenames = []
2✔
1312
        self.featurecatalogues = []
2✔
1313
        if fcd is None:
2✔
1314
            self.xml = None
2✔
1315
            self.compliancecode = None
2✔
1316
            self.language = []
2✔
1317
            self.includedwithdataset = None
2✔
1318
            self.featuretypenames = []
2✔
1319
            self.featurecatalogues = []
2✔
1320

1321
        else:
1322
            if hasattr(fcd, 'getroot'):  # standalone document
2✔
1323
                self.xml = etree.tostring(fcd.getroot())
×
1324
            else:  # part of a larger document
1325
                self.xml = etree.tostring(fcd)
2✔
1326

1327
            self.compliancecode = None
2✔
1328
            comp = fcd.find(util.nspath_eval('mrc:complianceCode/gco:Boolean', self.namespaces))
2✔
1329
            val = util.testXMLValue(comp)
2✔
1330
            if val is not None:
2✔
1331
                self.compliancecode = util.getTypedValue('boolean', val)
2✔
1332

1333
            self.language = []
2✔
1334
            for i in fcd.findall(util.nspath_eval('mrc:locale/lan:PT_Locale/lan:language/lan:LanguageCode', self.namespaces)):
2✔
1335
                val = _testCodeListValue(i)
2✔
1336
                if val is not None:
2✔
1337
                    self.language.append(val)
2✔
1338

1339
            self.includedwithdataset = None
2✔
1340
            comp = fcd.find(util.nspath_eval('mrc:includedWithDataset/gco:Boolean', self.namespaces))
2✔
1341
            val = util.testXMLValue(comp)
2✔
1342
            if val is not None:
2✔
1343
                self.includedwithdataset = util.getTypedValue('boolean', val)
2✔
1344

1345
            self.featuretypenames = []
2✔
1346
            for name in fcd.xpath('mrc:featureTypes/mrc:MD_FeatureTypeInfo/mrc:featureTypeName/*[self::gco:LocalName or self::gco:ScopedName]',
2✔
1347
                                   namespaces=self.namespaces):
1348
                val = util.testXMLValue(name)
2✔
1349
                if ValueError is not None:
2✔
1350
                    self.featuretypenames.append(val)
2✔
1351

1352
            # Gather feature catalogue titles
1353
            self.featurecatalogues = []
2✔
1354
            for cit in fcd.findall(util.nspath_eval(
2✔
1355
                    'mrc:featureCatalogueCitation/cit:CI_Citation/cit:title/gco:CharacterString', self.namespaces)):
1356
                val = util.testXMLValue(cit)
2✔
1357
                if val is not None:
2✔
1358
                    self.featurecatalogues.append(val)
2✔
1359

1360

1361
class FC_FeatureCatalogue(object):
2✔
1362
    """Process gfc:FC_FeatureCatalogue"""
1363
    def __init__(self, fc=None):
2✔
1364
        if fc is None:
×
1365
            self.xml = None
×
1366
            self.identifier = None
×
1367
            self.name = None
×
1368
            self.versiondate = None
×
1369
            self.producer = None
×
1370
            self.featuretypes = []
×
1371
        else:
1372
            if hasattr(fc, 'getroot'):  # standalone document
×
1373
                self.xml = etree.tostring(fc.getroot())
×
1374
            else:  # part of a larger document
1375
                self.xml = etree.tostring(fc)
×
1376

1377
            val = fc.attrib['uuid']
×
1378
            self.identifier = util.testXMLValue(val, attrib=True)
×
1379

1380
            val = fc.find(util.nspath_eval('cat:name/gco:CharacterString', self.namespaces))
×
1381
            self.name = util.testXMLValue(val)
×
1382

1383
            val = fc.find(util.nspath_eval('cat:versionDate/gco:Date', self.namespaces))
×
1384
            self.versiondate = util.testXMLValue(val)
×
1385

1386
            if not self.versiondate:
×
1387
                val = fc.find(util.nspath_eval('cat:versionDate/gco:DateTime', self.namespaces))
×
1388
                self.versiondate = util.testXMLValue(val)
×
1389

1390
            self.producer = None
×
1391
            prod = fc.find(util.nspath_eval('gfc:producer/cit:CI_Responsiblility', self.namespaces))
×
1392
            if prod is not None:
×
1393
                self.producer = CI_Responsibility(prod)
×
1394

1395
            self.featuretypes = []
×
1396
            for i in fc.findall(util.nspath_eval('gfc:featureType/gfc:FC_FeatureType', self.namespaces)):
×
1397
                self.featuretypes.append(FC_FeatureType(i))
×
1398

1399
class FC_FeatureType(object):
2✔
1400
    """Process gfc:FC_FeatureType"""
1401
    def __init__(self, ft=None):
2✔
1402
        if ft is None:
×
1403
            self.xml = None
×
1404
            self.identifier = None
×
1405
            self.typename = None
×
1406
            self.definition = None
×
1407
            self.isabstract = None
×
1408
            self.aliases = []
×
1409
            self.attributes = []
×
1410
        else:
1411
            if hasattr(ft, 'getroot'):  # standalone document
×
1412
                self.xml = etree.tostring(ft.getroot())
×
1413
            else:  # part of a larger document
1414
                self.xml = etree.tostring(ft)
×
1415

1416
            val = ft.attrib['uuid']
×
1417
            self.identifier = util.testXMLValue(val, attrib=True)
×
1418

1419
            val = ft.find(util.nspath_eval('gfc:typeName/gco:LocalName', self.namespaces))
×
1420
            self.typename = util.testXMLValue(val)
×
1421

1422
            val = ft.find(util.nspath_eval('gfc:definition/gco:CharacterString', self.namespaces))
×
1423
            self.definition = util.testXMLValue(val)
×
1424

1425
            self.isabstract = None
×
1426
            val = ft.find(util.nspath_eval('gfc:isAbstract/gco:Boolean', self.namespaces))
×
1427
            val = util.testXMLValue(val)
×
1428
            if val is not None:
×
1429
                self.isabstract = util.getTypedValue('boolean', val)
×
1430

1431
            self.aliases = []
×
1432
            for i in ft.findall(util.nspath_eval('gfc:aliases/gco:LocalName', self.namespaces)):
×
1433
                self.aliases.append(util.testXMLValue(i))
×
1434

1435
            self.attributes = []
×
1436
            for i in ft.findall(util.nspath_eval('gfc:carrierOfCharacteristics/gfc:FC_FeatureAttribute', namespaces)):
×
1437
                self.attributes.append(FC_FeatureAttribute(i))
×
1438

1439
class FC_FeatureAttribute(object):
2✔
1440
    """Process gfc:FC_FeatureAttribute"""
1441
    def __init__(self, fa=None):
2✔
1442
        if fa is None:
×
1443
            self.xml = None
×
1444
            self.membername = None
×
1445
            self.definition = None
×
1446
            self.code = None
×
1447
            self.valuetype = None
×
1448
            self.listedvalues = []
×
1449
        else:
1450
            if hasattr(fa, 'getroot'):  # standalone document
×
1451
                self.xml = etree.tostring(fa.getroot())
×
1452
            else:  # part of a larger document
1453
                self.xml = etree.tostring(fa)
×
1454

1455
            val = fa.find(util.nspath_eval('gfc:memberName/gco:ScopedName', self.namespaces))
×
1456
            self.membername = util.testXMLValue(val)
×
1457

1458
            val = fa.find(util.nspath_eval('gfc:definition/gco:CharacterString', self.namespaces))
×
1459
            self.definition = util.testXMLValue(val)
×
1460

1461
            val = fa.find(util.nspath_eval('gfc:code/gco:CharacterString', self.namespaces))
×
1462
            self.code = util.testXMLValue(val)
×
1463

1464
            val = fa.find(util.nspath_eval('gfc:valueType/gco:TypeName/gco:aName/gco:CharacterString', self.namespaces))
×
1465
            self.valuetype = util.testXMLValue(val)
×
1466

1467
            self.listedvalues = []
×
1468
            for i in fa.findall(util.nspath_eval('gfc:listedValue/gfc:FC_ListedValue', self.namespaces)):
×
1469
                self.listedvalues.append(FC_ListedValue(i))
×
1470

1471
class FC_ListedValue(object):
2✔
1472
    """Process gfc:FC_ListedValue"""
1473
    def __init__(self, lv=None):
2✔
1474
        if lv is None:
×
1475
            self.xml = None
×
1476
            self.label = None
×
1477
            self.code = None
×
1478
            self.definition = None
×
1479
        else:
1480
            if hasattr(lv, 'getroot'):  # standalone document
×
1481
                self.xml = etree.tostring(lv.getroot())
×
1482
            else:  # part of a larger document
1483
                self.xml = etree.tostring(lv)
×
1484

1485
            val = lv.find(util.nspath_eval('gfc:label/gco:CharacterString', self.namespaces))
×
1486
            self.label = util.testXMLValue(val)
×
1487

1488
            val = lv.find(util.nspath_eval('gfc:code/gco:CharacterString', self.namespaces))
×
1489
            self.code = util.testXMLValue(val)
×
1490

1491
            val = lv.find(util.nspath_eval('gfc:definition/gco:CharacterString', self.namespaces))
×
1492
            self.definition = util.testXMLValue(val)
×
1493

1494
class MD_ImageDescription(printable):
2✔
1495
    """Process mrc:MD_ImageDescription
1496
    """
1497
    def __init__(self, namespaces, img_desc=None):
2✔
1498
        """
1499
        Parses MD_ImageDescription XML subtree
1500

1501
        :param namespaces: dict of XML namespaces, key is namespace, val is path
1502
        :param img_desc: MD_ImageDescription etree.Element
1503
        """
1504
        self.namespaces = namespaces
2✔
1505
        self.type = 'image'
2✔
1506
        self.bands = []
2✔
1507

1508
        if img_desc is None:
2✔
1509
            self.attributedescription = None
2✔
1510
            self.cloudcover = None
2✔
1511
            self.processinglevel = None
2✔
1512
        else:
1513
            attdesc = img_desc.find(util.nspath_eval('mrc:attributeDescription/gco:RecordType', self.namespaces))
2✔
1514
            self.attributedescription = util.testXMLValue(attdesc)
2✔
1515

1516
            ctype = img_desc.find(util.nspath_eval('mrc:attributeGroup/mrc:MD_AttributeGroup/mrc:contentType/mrc:MD_CoverageContentTypeCode', self.namespaces))
2✔
1517
            self.type = util.testXMLAttribute(ctype, 'codeListValue')
2✔
1518

1519
            cloudcov = img_desc.find(util.nspath_eval('mrc:cloudCoverPercentage/gco:Real', self.namespaces))
2✔
1520
            self.cloudcover = util.testXMLValue(cloudcov)
2✔
1521

1522
            proclvl = img_desc.find(util.nspath_eval(
2✔
1523
                'mrc:processingLevelCode/mcc:MD_Identifier/mcc:code/gco:CharacterString', self.namespaces))
1524
            self.processinglevel = util.testXMLValue(proclvl)
2✔
1525

1526
            for i in img_desc.findall(util.nspath_eval('mrc:attributeGroup/mrc:MD_AttributeGroup/mrc:attribute/mrc:MD_Band', self.namespaces)):
2✔
1527
                self.bands.append(MD_Band(self.namespaces, i))
2✔
1528

1529

1530
class MD_Band(printable):
2✔
1531
    """Process mrc:MD_Band
1532
    """
1533
    def __init__(self, namespaces, band):
2✔
1534
        """
1535
        Parses MD_Band XML subtree
1536

1537
        :param namespaces: dict of XML namespaces, key is namespace, val is path
1538
        :param band: MD_Band etree.Element
1539
        """
1540
        self.namespaces = namespaces
2✔
1541
        if band is None:
2✔
1542
            self.id = None
2✔
1543
            self.units = None
2✔
1544
            self.min = None
2✔
1545
            self.max = None
2✔
1546
        else:
1547
            seq_id = band.find(util.nspath_eval('mrc:sequenceIdentifier/gco:MemberName/gco:aName/gco:CharacterString', self.namespaces))
2✔
1548
            self.id = util.testXMLValue(seq_id)
2✔
1549

1550
            units = band.find(util.nspath_eval('mrc:units/gml:UnitDefinition/gml:identifier', self.namespaces))
2✔
1551
            self.units = util.testXMLValue(units)
2✔
1552

1553
            bmin = band.find(util.nspath_eval('mrc:minValue/gco:Real', self.namespaces))
2✔
1554
            self.min = util.testXMLValue(bmin)
2✔
1555

1556
            bmax = band.find(util.nspath_eval('mrc:maxValue/gco:Real', self.namespaces))
2✔
1557
            self.max = util.testXMLValue(bmax)
2✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc