Coverage for gws-app/gws/plugin/ows_server/csw/templates/iso/record.py: 94%
68 statements
« prev ^ index » next coverage.py v7.16.2, created at 2026-10-05 13:35 +0200
« prev ^ index » next coverage.py v7.16.2, created at 2026-10-05 13:35 +0200
1"""CSW record builder for the ISO profile (``gmd:MD_Metadata``)."""
3import gws
4import gws.base.ows.server as server
5import gws.base.ows.server.templatelib as tpl
6from gws.lib.xmlx import tag
8ML_GMX_CODELISTS = 'http://standards.iso.org/iso/19139/resources/gmxCodelists.xml'
11def record(ta: server.TemplateArgs, md: gws.Metadata):
12 """Create a ``gmd:MD_Metadata`` element for a metadata object.
14 Args:
15 ta: Template arguments.
16 md: Metadata of the catalog record.
18 Returns:
19 The XML element.
20 """
21 def w_code(wrap, lst, value, text=None):
22 return tag(
23 f'GMD:{wrap}/GMD:{lst}',
24 {'codeList': ML_GMX_CODELISTS + '#' + lst, 'codeListValue': value},
25 text or value,
26 )
28 def w_date(d, typ):
29 return tag(
30 'GMD:date/GMD:CI_Date',
31 tag('GMD:date/GCO:Date', tpl.iso_date(d)),
32 w_code(
33 'dateType',
34 'CI_DateTypeCode',
35 typ,
36 ),
37 )
39 def w_lang():
40 return tag(
41 'GMD:language/GMD:LanguageCode',
42 {'codeList': 'http://www.loc.gov/standards/iso639-2/', 'codeListValue': md.language3},
43 md.languageName,
44 )
46 def w_bbox(ext):
47 return tag(
48 'GMD:EX_GeographicBoundingBox',
49 tag('GMD:westBoundLongitude/GCO:Decimal', tpl.coord_dms(ext[0])),
50 tag('GMD:eastBoundLongitude/GCO:Decimal', tpl.coord_dms(ext[2])),
51 tag('GMD:southBoundLatitude/GCO:Decimal', tpl.coord_dms(ext[1])),
52 tag('GMD:northBoundLatitude/GCO:Decimal', tpl.coord_dms(ext[3])),
53 )
55 def contact():
56 yield tag(
57 'GMD:CI_ResponsibleParty',
58 tag('GMD:organisationName/GCO:CharacterString', md.contactOrganization),
59 tag('GMD:positionName/GCO:CharacterString', md.contactPosition),
60 tag(
61 'GMD:contactInfo/GMD:CI_Contact',
62 tag(
63 'GMD:phone/GMD:CI_Telephone',
64 tag('GMD:voice/GCO:CharacterString', md.contactPhone),
65 tag('GMD:facsimile/GCO:CharacterString', md.contactFax),
66 ),
67 tag(
68 'GMD:address/GMD:CI_Address',
69 tag('GMD:deliveryPoint/GCO:CharacterString', md.contactAddress),
70 tag('GMD:city/GCO:CharacterString', md.contactCity),
71 tag('GMD:administrativeArea/GCO:CharacterString', md.contactArea),
72 tag('GMD:postalCode/GCO:CharacterString', md.contactZip),
73 tag('GMD:country/GCO:CharacterString', md.contactCountry),
74 tag('GMD:electronicMailAddress/GCO:CharacterString', md.contactEmail),
75 ),
76 tag('GMD:onlineResource/GMD:CI_OnlineResource/GMD:linkage/GMD:URL', md.contactUrl),
77 ),
78 w_code('role', 'CI_RoleCode', md.contactRole),
79 )
81 def identification():
82 yield tag(
83 'GMD:citation/GMD:CI_Citation',
84 tag('GMD:title/GCO:CharacterString', md.title),
85 w_date(md.dateCreated, 'publication'),
86 w_date(md.dateUpdated, 'revision'),
87 tag('GMD:identifier/GMD:MD_Identifier/GMD:code/GCO:CharacterString', md.catalogCitationUid),
88 )
90 yield tag('GMD:abstract/GCO:CharacterString', md.abstract)
92 yield tag('GMD:pointOfContact', contact())
94 if md.inspireSpatialScope:
95 lst = 'http://inspire.ec.europa.eu/metadata-codelist/SpatialScope/'
96 yield tag(
97 'GMD:descriptiveKeywords/GMD:MD_Keywords',
98 tag('GMD:keyword/GMX:Anchor', {'XLINK:href': lst + md.inspireSpatialScope}, md.inspireSpatialScopeName),
99 tag(
100 'GMD:thesaurusName/GMD:CI_Citation',
101 tag('GMD:title/GMX:Anchor', {'XLINK:href': lst + 'SpatialScope'}, 'Spatial scope'),
102 w_date('2019-05-22', 'publication'),
103 ),
104 )
106 if md.inspireTheme:
107 yield tag(
108 'GMD:descriptiveKeywords/GMD:MD_Keywords',
109 tag('GMD:keyword/GCO:CharacterString', md.inspireThemeNameEn),
110 w_code('type', 'MD_KeywordTypeCode', 'theme'),
111 tag(
112 'GMD:thesaurusName/GMD:CI_Citation',
113 tag('GMD:title/GCO:CharacterString', 'GEMET - INSPIRE themes, version 1.0'),
114 w_date('2008-06-01', 'publication'),
115 ),
116 )
118 if md.keywords:
119 yield tag(
120 'GMD:descriptiveKeywords/GMD:MD_Keywords',
121 [tag('GMD:keyword/GCO:CharacterString', kw) for kw in md.keywords],
122 )
124 yield tag(
125 'GMD:resourceConstraints/GMD:MD_LegalConstraints',
126 w_code('useConstraints', 'MD_RestrictionCode', 'otherRestrictions'),
127 tag('GMD:otherConstraints/GCO:CharacterString', md.accessConstraints),
128 tag('GMD:otherConstraints/GCO:CharacterString', md.license),
129 )
131 yield w_code(
132 'spatialRepresentationType',
133 'MD_SpatialRepresentationTypeCode',
134 md.isoSpatialRepresentationType,
135 )
137 if md.isoSpatialResolution:
138 yield tag(
139 'GMD:spatialResolution/GMD:MD_Resolution/GMD:equivalentScale/GMD:MD_RepresentativeFraction/GMD:denominator/GCO:Integer',
140 md.isoSpatialResolution,
141 )
143 yield w_lang()
144 yield w_code('characterSet', 'MD_CharacterSetCode', 'utf8')
146 if md.isoTopicCategories:
147 for cat in md.isoTopicCategories:
148 yield tag('GMD:topicCategory/GMD:MD_TopicCategoryCode', cat)
150 if md.wgsExtent:
151 yield tag('GMD:extent/GMD:EX_Extent/GMD:geographicElement', w_bbox(md.wgsExtent))
153 # @TODO
154 # if md.bounding_polygon_element:
155 # yield (
156 # 'GMD:extent/GMD:EX_Extent/GMD:geographicElement/GMD:EX_BoundingPolygon/GMD:polygon',
157 # md.bounding_polygon_element
158 # )
160 if md.temporalBegin:
161 yield tag(
162 'GMD:extent/GMD:EX_Extent/GMD:temporalElement/GMD:EX_TemporalExtent/GMD:extent/GML:TimePeriod',
163 tag('GML:beginPosition', md.temporalBegin),
164 tag('GML:endPosition', md.temporalEnd),
165 )
167 def distributionInfo():
168 for link in md.metaLinks:
169 if link.format:
170 yield tag(
171 'GMD:distributionFormat/GMD:MD_Format',
172 tag('GMD:name/GCO:CharacterString', link.format),
173 tag('GMD:version/GCO:CharacterString', link.formatVersion),
174 )
176 for link in md.metaLinks:
177 yield tag(
178 'GMD:transferOptions/GMD:MD_DigitalTransferOptions',
179 tag(
180 'GMD:onLine/GMD:CI_OnlineResource',
181 tag('GMD:linkage/GMD:URL', ta.url_for(link.url)),
182 w_code('function', 'CI_OnLineFunctionCode', link.function),
183 ),
184 )
186 def dataQualityInfo():
187 yield tag('GMD:scope/GMD:DQ_Scope', w_code('level', 'MD_ScopeCode', md.isoScope))
189 if md.isoQualityConformanceQualityPass:
190 yield tag(
191 'GMD:report/GMD:DQ_DomainConsistency/GMD:result/GMD:DQ_ConformanceResult',
192 tag(
193 'GMD:specification/GMD:CI_Citation',
194 tag('GMD:title/GCO:CharacterString', md.isoQualityConformanceSpecificationTitle),
195 w_date(md.isoQualityConformanceSpecificationDate, 'publication'),
196 ),
197 tag('GMD:explanation/GCO:CharacterString', md.isoQualityConformanceExplanation),
198 tag('GMD:pass/GCO:Boolean', md.isoQualityConformanceQualityPass),
199 )
201 if md.isoQualityLineageStatement:
202 yield tag(
203 'GMD:lineage/GMD:LI_Lineage',
204 tag('GMD:statement/GCO:CharacterString', md.isoQualityLineageStatement),
205 tag(
206 'GMD:source/GMD:LI_Source',
207 tag('GMD:description/GCO:CharacterString', md.isoQualityLineageSource),
208 tag('GMD:scaleDenominator/GMD:MD_RepresentativeFraction/GMD:denominator/GCO:Integer', md.isoQualityLineageSourceScale),
209 ),
210 )
212 def content():
213 yield tag('GMD:fileIdentifier/GCO:CharacterString', md.catalogUid)
215 w_lang()
216 yield w_code('characterSet', 'MD_CharacterSetCode', 'utf8')
218 yield w_code('hierarchyLevel', 'MD_ScopeCode', md.isoScope)
219 yield tag('GMD:hierarchyLevelName/GCO:CharacterString', md.isoScopeName)
221 yield tag('GMD:contact', contact())
223 yield tag('GMD:dateStamp/GCO:Date', tpl.iso_date(md.dateUpdated))
225 yield tag('GMD:metadataStandardName/GCO:CharacterString', 'ISO19115')
226 yield tag('GMD:metadataStandardVersion/GCO:CharacterString', '2003/Cor.1:2006')
228 if md.crs:
229 yield tag(
230 'GMD:referenceSystemInfo/GMD:MD_ReferenceSystem/GMD:referenceSystemIdentifier/GMD:RS_Identifier/GMD:code/GCO:CharacterString',
231 md.crs.uri,
232 )
234 yield tag('GMD:identificationInfo/GMD:MD_DataIdentification', identification())
235 yield tag('GMD:distributionInfo/GMD:MD_Distribution', distributionInfo())
236 yield tag('GMD:dataQualityInfo/GMD:DQ_DataQuality', dataQualityInfo())
238 ##
240 return tag('GMD:MD_Metadata', content())