geonode-pycsw 3.0.0b2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (382) hide show
  1. geonode_pycsw-3.0.0b2.dist-info/METADATA +73 -0
  2. geonode_pycsw-3.0.0b2.dist-info/RECORD +382 -0
  3. geonode_pycsw-3.0.0b2.dist-info/WHEEL +5 -0
  4. geonode_pycsw-3.0.0b2.dist-info/entry_points.txt +2 -0
  5. geonode_pycsw-3.0.0b2.dist-info/licenses/LICENSE.txt +26 -0
  6. geonode_pycsw-3.0.0b2.dist-info/top_level.txt +1 -0
  7. pycsw/__init__.py +33 -0
  8. pycsw/core/__init__.py +29 -0
  9. pycsw/core/admin.py +703 -0
  10. pycsw/core/config.py +621 -0
  11. pycsw/core/etree.py +43 -0
  12. pycsw/core/formats/__init__.py +29 -0
  13. pycsw/core/formats/fmt_json.py +69 -0
  14. pycsw/core/log.py +71 -0
  15. pycsw/core/metadata.py +1996 -0
  16. pycsw/core/pygeofilter_evaluate.py +83 -0
  17. pycsw/core/repository.py +941 -0
  18. pycsw/core/schemas/ogc/cat/csw/3.0/_wrapper.xsd +14 -0
  19. pycsw/core/schemas/ogc/cat/csw/3.0/cswAll.xsd +33 -0
  20. pycsw/core/schemas/ogc/cat/csw/3.0/cswCommon.xsd +71 -0
  21. pycsw/core/schemas/ogc/cat/csw/3.0/cswGetCapabilities.xsd +80 -0
  22. pycsw/core/schemas/ogc/cat/csw/3.0/cswGetDomain.xsd +146 -0
  23. pycsw/core/schemas/ogc/cat/csw/3.0/cswGetRecordById.xsd +58 -0
  24. pycsw/core/schemas/ogc/cat/csw/3.0/cswGetRecords.xsd +391 -0
  25. pycsw/core/schemas/ogc/cat/csw/3.0/cswHarvest.xsd +95 -0
  26. pycsw/core/schemas/ogc/cat/csw/3.0/cswTransaction.xsd +187 -0
  27. pycsw/core/schemas/ogc/cat/csw/3.0/cswUnHarvest.xsd +77 -0
  28. pycsw/core/schemas/ogc/cat/csw/3.0/rec-dcmes.xsd +245 -0
  29. pycsw/core/schemas/ogc/cat/csw/3.0/rec-dcterms.xsd +101 -0
  30. pycsw/core/schemas/ogc/cat/csw/3.0/record.xsd +170 -0
  31. pycsw/core/schemas/ogc/csw/2.0.2/CSW-discovery.xsd +494 -0
  32. pycsw/core/schemas/ogc/csw/2.0.2/CSW-publication.xsd +242 -0
  33. pycsw/core/schemas/ogc/csw/2.0.2/rec-dcmes.xsd +199 -0
  34. pycsw/core/schemas/ogc/csw/2.0.2/rec-dcterms.xsd +94 -0
  35. pycsw/core/schemas/ogc/csw/2.0.2/record.xsd +138 -0
  36. pycsw/core/schemas/ogc/filter/1.1.0/expr.xsd +67 -0
  37. pycsw/core/schemas/ogc/filter/1.1.0/filter.xsd +265 -0
  38. pycsw/core/schemas/ogc/filter/1.1.0/filterCapabilities.xsd +171 -0
  39. pycsw/core/schemas/ogc/filter/1.1.0/sort.xsd +46 -0
  40. pycsw/core/schemas/ogc/filter/2.0/_wrapper.xsd +5 -0
  41. pycsw/core/schemas/ogc/filter/2.0/expr.xsd +44 -0
  42. pycsw/core/schemas/ogc/filter/2.0/filter.xsd +396 -0
  43. pycsw/core/schemas/ogc/filter/2.0/filterAll.xsd +23 -0
  44. pycsw/core/schemas/ogc/filter/2.0/filterCapabilities.xsd +286 -0
  45. pycsw/core/schemas/ogc/filter/2.0/query.xsd +70 -0
  46. pycsw/core/schemas/ogc/filter/2.0/sort.xsd +49 -0
  47. pycsw/core/schemas/ogc/gml/3.1.1/base/basicTypes.xsd +278 -0
  48. pycsw/core/schemas/ogc/gml/3.1.1/base/coordinateOperations.xsd +789 -0
  49. pycsw/core/schemas/ogc/gml/3.1.1/base/coordinateReferenceSystems.xsd +429 -0
  50. pycsw/core/schemas/ogc/gml/3.1.1/base/coordinateSystems.xsd +408 -0
  51. pycsw/core/schemas/ogc/gml/3.1.1/base/coverage.xsd +451 -0
  52. pycsw/core/schemas/ogc/gml/3.1.1/base/dataQuality.xsd +129 -0
  53. pycsw/core/schemas/ogc/gml/3.1.1/base/datums.xsd +484 -0
  54. pycsw/core/schemas/ogc/gml/3.1.1/base/defaultStyle.xsd +454 -0
  55. pycsw/core/schemas/ogc/gml/3.1.1/base/dictionary.xsd +137 -0
  56. pycsw/core/schemas/ogc/gml/3.1.1/base/direction.xsd +72 -0
  57. pycsw/core/schemas/ogc/gml/3.1.1/base/dynamicFeature.xsd +115 -0
  58. pycsw/core/schemas/ogc/gml/3.1.1/base/feature.xsd +199 -0
  59. pycsw/core/schemas/ogc/gml/3.1.1/base/geometryAggregates.xsd +430 -0
  60. pycsw/core/schemas/ogc/gml/3.1.1/base/geometryBasic0d1d.xsd +602 -0
  61. pycsw/core/schemas/ogc/gml/3.1.1/base/geometryBasic2d.xsd +213 -0
  62. pycsw/core/schemas/ogc/gml/3.1.1/base/geometryComplexes.xsd +141 -0
  63. pycsw/core/schemas/ogc/gml/3.1.1/base/geometryPrimitives.xsd +1609 -0
  64. pycsw/core/schemas/ogc/gml/3.1.1/base/gml.xsd +22 -0
  65. pycsw/core/schemas/ogc/gml/3.1.1/base/gmlBase.xsd +294 -0
  66. pycsw/core/schemas/ogc/gml/3.1.1/base/grids.xsd +76 -0
  67. pycsw/core/schemas/ogc/gml/3.1.1/base/measures.xsd +200 -0
  68. pycsw/core/schemas/ogc/gml/3.1.1/base/observation.xsd +96 -0
  69. pycsw/core/schemas/ogc/gml/3.1.1/base/referenceSystems.xsd +211 -0
  70. pycsw/core/schemas/ogc/gml/3.1.1/base/temporal.xsd +332 -0
  71. pycsw/core/schemas/ogc/gml/3.1.1/base/temporalReferenceSystems.xsd +251 -0
  72. pycsw/core/schemas/ogc/gml/3.1.1/base/temporalTopology.xsd +186 -0
  73. pycsw/core/schemas/ogc/gml/3.1.1/base/topology.xsd +459 -0
  74. pycsw/core/schemas/ogc/gml/3.1.1/base/units.xsd +170 -0
  75. pycsw/core/schemas/ogc/gml/3.1.1/base/valueObjects.xsd +361 -0
  76. pycsw/core/schemas/ogc/gml/3.1.1/smil/smil20-language.xsd +117 -0
  77. pycsw/core/schemas/ogc/gml/3.1.1/smil/smil20.xsd +234 -0
  78. pycsw/core/schemas/ogc/gml/3.2.1/basicTypes.xsd +268 -0
  79. pycsw/core/schemas/ogc/gml/3.2.1/coordinateOperations.xsd +525 -0
  80. pycsw/core/schemas/ogc/gml/3.2.1/coordinateReferenceSystems.xsd +373 -0
  81. pycsw/core/schemas/ogc/gml/3.2.1/coordinateSystems.xsd +297 -0
  82. pycsw/core/schemas/ogc/gml/3.2.1/coverage.xsd +292 -0
  83. pycsw/core/schemas/ogc/gml/3.2.1/datums.xsd +287 -0
  84. pycsw/core/schemas/ogc/gml/3.2.1/defaultStyle.xsd +453 -0
  85. pycsw/core/schemas/ogc/gml/3.2.1/deprecatedTypes.xsd +1133 -0
  86. pycsw/core/schemas/ogc/gml/3.2.1/dictionary.xsd +90 -0
  87. pycsw/core/schemas/ogc/gml/3.2.1/direction.xsd +84 -0
  88. pycsw/core/schemas/ogc/gml/3.2.1/dynamicFeature.xsd +109 -0
  89. pycsw/core/schemas/ogc/gml/3.2.1/feature.xsd +94 -0
  90. pycsw/core/schemas/ogc/gml/3.2.1/geometryAggregates.xsd +197 -0
  91. pycsw/core/schemas/ogc/gml/3.2.1/geometryBasic0d1d.xsd +277 -0
  92. pycsw/core/schemas/ogc/gml/3.2.1/geometryBasic2d.xsd +124 -0
  93. pycsw/core/schemas/ogc/gml/3.2.1/geometryComplexes.xsd +95 -0
  94. pycsw/core/schemas/ogc/gml/3.2.1/geometryPrimitives.xsd +846 -0
  95. pycsw/core/schemas/ogc/gml/3.2.1/gml.xsd +20 -0
  96. pycsw/core/schemas/ogc/gml/3.2.1/gmlBase.xsd +185 -0
  97. pycsw/core/schemas/ogc/gml/3.2.1/grids.xsd +64 -0
  98. pycsw/core/schemas/ogc/gml/3.2.1/measures.xsd +68 -0
  99. pycsw/core/schemas/ogc/gml/3.2.1/observation.xsd +95 -0
  100. pycsw/core/schemas/ogc/gml/3.2.1/referenceSystems.xsd +70 -0
  101. pycsw/core/schemas/ogc/gml/3.2.1/temporal.xsd +269 -0
  102. pycsw/core/schemas/ogc/gml/3.2.1/temporalReferenceSystems.xsd +189 -0
  103. pycsw/core/schemas/ogc/gml/3.2.1/temporalTopology.xsd +119 -0
  104. pycsw/core/schemas/ogc/gml/3.2.1/topology.xsd +386 -0
  105. pycsw/core/schemas/ogc/gml/3.2.1/units.xsd +162 -0
  106. pycsw/core/schemas/ogc/gml/3.2.1/valueObjects.xsd +205 -0
  107. pycsw/core/schemas/ogc/ogcapi/records/part1/1.0/ogcapi-records-1.yaml +932 -0
  108. pycsw/core/schemas/ogc/ows/1.0.0/ows19115subset.xsd +222 -0
  109. pycsw/core/schemas/ogc/ows/1.0.0/owsAll.xsd +20 -0
  110. pycsw/core/schemas/ogc/ows/1.0.0/owsCommon.xsd +155 -0
  111. pycsw/core/schemas/ogc/ows/1.0.0/owsDataIdentification.xsd +112 -0
  112. pycsw/core/schemas/ogc/ows/1.0.0/owsExceptionReport.xsd +67 -0
  113. pycsw/core/schemas/ogc/ows/1.0.0/owsGetCapabilities.xsd +108 -0
  114. pycsw/core/schemas/ogc/ows/1.0.0/owsOperationsMetadata.xsd +161 -0
  115. pycsw/core/schemas/ogc/ows/1.0.0/owsServiceIdentification.xsd +55 -0
  116. pycsw/core/schemas/ogc/ows/1.0.0/owsServiceProvider.xsd +46 -0
  117. pycsw/core/schemas/ogc/ows/1.1.0/ows19115subset.xsd +236 -0
  118. pycsw/core/schemas/ogc/ows/1.1.0/owsAll.xsd +23 -0
  119. pycsw/core/schemas/ogc/ows/1.1.0/owsCommon.xsd +158 -0
  120. pycsw/core/schemas/ogc/ows/1.1.0/owsContents.xsd +87 -0
  121. pycsw/core/schemas/ogc/ows/1.1.0/owsDataIdentification.xsd +128 -0
  122. pycsw/core/schemas/ogc/ows/1.1.0/owsDomainType.xsd +280 -0
  123. pycsw/core/schemas/ogc/ows/1.1.0/owsExceptionReport.xsd +77 -0
  124. pycsw/core/schemas/ogc/ows/1.1.0/owsGetCapabilities.xsd +113 -0
  125. pycsw/core/schemas/ogc/ows/1.1.0/owsGetResourceByID.xsd +52 -0
  126. pycsw/core/schemas/ogc/ows/1.1.0/owsInputOutputData.xsd +60 -0
  127. pycsw/core/schemas/ogc/ows/1.1.0/owsManifest.xsd +125 -0
  128. pycsw/core/schemas/ogc/ows/1.1.0/owsOperationsMetadata.xsd +141 -0
  129. pycsw/core/schemas/ogc/ows/1.1.0/owsServiceIdentification.xsd +61 -0
  130. pycsw/core/schemas/ogc/ows/1.1.0/owsServiceProvider.xsd +48 -0
  131. pycsw/core/schemas/ogc/ows/2.0/ows19115subset.xsd +364 -0
  132. pycsw/core/schemas/ogc/ows/2.0/owsAdditionalParameters.xsd +114 -0
  133. pycsw/core/schemas/ogc/ows/2.0/owsAll.xsd +29 -0
  134. pycsw/core/schemas/ogc/ows/2.0/owsCommon.xsd +275 -0
  135. pycsw/core/schemas/ogc/ows/2.0/owsContents.xsd +163 -0
  136. pycsw/core/schemas/ogc/ows/2.0/owsDataIdentification.xsd +202 -0
  137. pycsw/core/schemas/ogc/ows/2.0/owsDomainType.xsd +388 -0
  138. pycsw/core/schemas/ogc/ows/2.0/owsExceptionReport.xsd +126 -0
  139. pycsw/core/schemas/ogc/ows/2.0/owsGetCapabilities.xsd +220 -0
  140. pycsw/core/schemas/ogc/ows/2.0/owsGetResourceByID.xsd +83 -0
  141. pycsw/core/schemas/ogc/ows/2.0/owsInputOutputData.xsd +98 -0
  142. pycsw/core/schemas/ogc/ows/2.0/owsManifest.xsd +181 -0
  143. pycsw/core/schemas/ogc/ows/2.0/owsOperationsMetadata.xsd +234 -0
  144. pycsw/core/schemas/ogc/ows/2.0/owsServiceIdentification.xsd +98 -0
  145. pycsw/core/schemas/ogc/ows/2.0/owsServiceProvider.xsd +64 -0
  146. pycsw/core/schemas/w3c/1999/xlink.xsd +271 -0
  147. pycsw/core/schemas/w3c/2001/xml.xsd +287 -0
  148. pycsw/core/util.py +552 -0
  149. pycsw/oaipmh.py +311 -0
  150. pycsw/ogc/__init__.py +29 -0
  151. pycsw/ogc/api/__init__.py +28 -0
  152. pycsw/ogc/api/oapi.py +558 -0
  153. pycsw/ogc/api/records.py +1414 -0
  154. pycsw/ogc/api/templates/_base.html +76 -0
  155. pycsw/ogc/api/templates/collection.html +44 -0
  156. pycsw/ogc/api/templates/collections.html +45 -0
  157. pycsw/ogc/api/templates/conformance.html +21 -0
  158. pycsw/ogc/api/templates/exception.html +8 -0
  159. pycsw/ogc/api/templates/item.html +228 -0
  160. pycsw/ogc/api/templates/items.html +337 -0
  161. pycsw/ogc/api/templates/landing_page.html +27 -0
  162. pycsw/ogc/api/templates/openapi.html +58 -0
  163. pycsw/ogc/api/templates/queryables.html +50 -0
  164. pycsw/ogc/api/templates/stac_items.html +173 -0
  165. pycsw/ogc/api/templates/static/favicon.ico +0 -0
  166. pycsw/ogc/api/templates/static/logo-horizontal.png +0 -0
  167. pycsw/ogc/api/templates/static/logo-vertical-darkbg.png +0 -0
  168. pycsw/ogc/api/util.py +252 -0
  169. pycsw/ogc/csw/__init__.py +29 -0
  170. pycsw/ogc/csw/cql.py +133 -0
  171. pycsw/ogc/csw/csw2.py +2042 -0
  172. pycsw/ogc/csw/csw3.py +2193 -0
  173. pycsw/ogc/fes/__init__.py +29 -0
  174. pycsw/ogc/fes/fes1.py +433 -0
  175. pycsw/ogc/fes/fes2.py +451 -0
  176. pycsw/ogc/gml/__init__.py +29 -0
  177. pycsw/ogc/gml/gml3.py +240 -0
  178. pycsw/ogc/gml/gml32.py +243 -0
  179. pycsw/opensearch.py +857 -0
  180. pycsw/plugins/__init__.py +29 -0
  181. pycsw/plugins/outputschemas/__init__.py +31 -0
  182. pycsw/plugins/outputschemas/atom.py +143 -0
  183. pycsw/plugins/outputschemas/datacite.py +375 -0
  184. pycsw/plugins/outputschemas/dif.py +213 -0
  185. pycsw/plugins/outputschemas/fgdc.py +180 -0
  186. pycsw/plugins/outputschemas/gm03.py +240 -0
  187. pycsw/plugins/profiles/__init__.py +29 -0
  188. pycsw/plugins/profiles/apiso/__init__.py +29 -0
  189. pycsw/plugins/profiles/apiso/apiso.py +757 -0
  190. pycsw/plugins/profiles/apiso/schemas/ogc/csw/2.0.2/profiles/apiso/1.0.0/apiso.xsd +13 -0
  191. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gco/basicTypes.xsd +429 -0
  192. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gco/gco.xsd +12 -0
  193. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gco/gcoBase.xsd +61 -0
  194. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gmd/applicationSchema.xsd +42 -0
  195. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gmd/citation.xsd +275 -0
  196. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gmd/constraints.xsd +106 -0
  197. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gmd/content.xsd +188 -0
  198. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gmd/dataQuality.xsd +554 -0
  199. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gmd/distribution.xsd +202 -0
  200. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gmd/extent.xsd +205 -0
  201. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gmd/freeText.xsd +122 -0
  202. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gmd/gmd.xsd +12 -0
  203. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gmd/identification.xsd +348 -0
  204. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gmd/maintenance.xsd +86 -0
  205. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gmd/metadataApplication.xsd +175 -0
  206. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gmd/metadataEntity.xsd +70 -0
  207. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gmd/metadataExtension.xsd +99 -0
  208. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gmd/portrayalCatalogue.xsd +36 -0
  209. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gmd/referenceSystem.xsd +100 -0
  210. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gmd/spatialRepresentation.xsd +237 -0
  211. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/basicTypes.xsd +267 -0
  212. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/coordinateOperations.xsd +639 -0
  213. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/coordinateReferenceSystems.xsd +526 -0
  214. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/coordinateSystems.xsd +401 -0
  215. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/coverage.xsd +462 -0
  216. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/datums.xsd +342 -0
  217. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/dictionary.xsd +129 -0
  218. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/direction.xsd +80 -0
  219. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/dynamicFeature.xsd +142 -0
  220. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/feature.xsd +215 -0
  221. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/geometryAggregates.xsd +216 -0
  222. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/geometryBasic0d1d.xsd +304 -0
  223. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/geometryBasic2d.xsd +123 -0
  224. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/geometryComplexes.xsd +89 -0
  225. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/geometryPrimitives.xsd +892 -0
  226. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/gml.xsd +14 -0
  227. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/gmlBase.xsd +305 -0
  228. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/grids.xsd +58 -0
  229. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/measures.xsd +167 -0
  230. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/observation.xsd +90 -0
  231. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/referenceSystems.xsd +69 -0
  232. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/temporal.xsd +263 -0
  233. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/temporalReferenceSystems.xsd +183 -0
  234. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/temporalTopology.xsd +124 -0
  235. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/topology.xsd +372 -0
  236. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/units.xsd +156 -0
  237. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/valueObjects.xsd +212 -0
  238. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gmx/catalogues.xsd +112 -0
  239. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gmx/codelistItem.xsd +168 -0
  240. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gmx/crsItem.xsd +1030 -0
  241. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gmx/extendedTypes.xsd +75 -0
  242. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gmx/gmx.xsd +2 -0
  243. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gmx/gmxUsage.xsd +127 -0
  244. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gmx/uomItem.xsd +162 -0
  245. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gsr/gsr.xsd +12 -0
  246. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gsr/spatialReferencing.xsd +24 -0
  247. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gss/geometry.xsd +35 -0
  248. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gss/gss.xsd +12 -0
  249. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gts/gts.xsd +12 -0
  250. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gts/temporalObjects.xsd +34 -0
  251. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/srv/serviceMetadata.xsd +197 -0
  252. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/srv/serviceModel.xsd +220 -0
  253. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/srv/srv.xsd +13 -0
  254. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gco/basicTypes.xsd +431 -0
  255. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gco/gco.xsd +12 -0
  256. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gco/gcoBase.xsd +63 -0
  257. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gmd/applicationSchema.xsd +43 -0
  258. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gmd/citation.xsd +276 -0
  259. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gmd/constraints.xsd +107 -0
  260. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gmd/content.xsd +190 -0
  261. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gmd/dataQuality.xsd +556 -0
  262. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gmd/distribution.xsd +203 -0
  263. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gmd/extent.xsd +206 -0
  264. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gmd/freeText.xsd +123 -0
  265. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gmd/gmd.xsd +12 -0
  266. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gmd/identification.xsd +349 -0
  267. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gmd/maintenance.xsd +87 -0
  268. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gmd/metadataApplication.xsd +176 -0
  269. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gmd/metadataEntity.xsd +71 -0
  270. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gmd/metadataExtension.xsd +100 -0
  271. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gmd/portrayalCatalogue.xsd +37 -0
  272. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gmd/referenceSystem.xsd +101 -0
  273. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gmd/spatialRepresentation.xsd +238 -0
  274. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gmx/catalogues.xsd +113 -0
  275. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gmx/codelistItem.xsd +169 -0
  276. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gmx/crsItem.xsd +1031 -0
  277. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gmx/extendedTypes.xsd +76 -0
  278. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gmx/gmx.xsd +12 -0
  279. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gmx/gmxUsage.xsd +128 -0
  280. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gmx/uomItem.xsd +163 -0
  281. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gsr/gsr.xsd +12 -0
  282. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gsr/spatialReferencing.xsd +25 -0
  283. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gss/geometry.xsd +36 -0
  284. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gss/gss.xsd +12 -0
  285. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gts/gts.xsd +12 -0
  286. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gts/temporalObjects.xsd +35 -0
  287. pycsw/plugins/profiles/ebrim/__init__.py +29 -0
  288. pycsw/plugins/profiles/ebrim/ebrim.py +174 -0
  289. pycsw/plugins/profiles/ebrim/schemas/ogc/csw/2.0.2/profiles/ebrim/1.0/csw-ebrim-iri.xsd +146 -0
  290. pycsw/plugins/profiles/ebrim/schemas/ogc/csw/2.0.2/profiles/ebrim/1.0/csw-ebrim.xsd +104 -0
  291. pycsw/plugins/profiles/iso19115p3/__init__.py +29 -0
  292. pycsw/plugins/profiles/iso19115p3/iso19115p3.py +854 -0
  293. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/cat/1.0/cat.xsd +13 -0
  294. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/cat/1.0/catalogues.xsd +53 -0
  295. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/cat/1.0/codelistItem.xsd +54 -0
  296. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/cat/1.0/crsItem.xsd +181 -0
  297. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/cat/1.0/uomItem.xsd +38 -0
  298. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/cit/1.0/cit.xsd +7 -0
  299. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/cit/1.0/citation.xsd +514 -0
  300. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/cit/2.0/cit.xsd +10 -0
  301. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/cit/2.0/citation.xsd +523 -0
  302. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/gco/1.0/baseTypes2014.xsd +530 -0
  303. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/gco/1.0/gco.xsd +16 -0
  304. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/gcx/1.0/extendedTypes.xsd +92 -0
  305. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/gcx/1.0/extendedTypes_autoFromShapeChange.xsd +60 -0
  306. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/gcx/1.0/gcx.xsd +8 -0
  307. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/gex/1.0/extent.xsd +241 -0
  308. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/gex/1.0/gex.xsd +9 -0
  309. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/gmw/1.0/gmlWrapperTypes2014.xsd +159 -0
  310. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/gmw/1.0/gmw.xsd +14 -0
  311. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/lan/1.0/lan.xsd +8 -0
  312. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/lan/1.0/language.xsd +140 -0
  313. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mac/1.0/acquisitionInformationImagery.xsd +622 -0
  314. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mac/1.0/mac.xsd +11 -0
  315. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mac/2.0/acquisitionInformationImagery.xsd +590 -0
  316. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mac/2.0/event.xsd +108 -0
  317. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mac/2.0/mac.xsd +10 -0
  318. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mas/1.0/applicationSchema.xsd +61 -0
  319. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mas/1.0/mas.xsd +9 -0
  320. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mcc/1.0/AbstractCommonClasses.xsd +358 -0
  321. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mcc/1.0/commonClasses.xsd +220 -0
  322. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mcc/1.0/mcc.xsd +8 -0
  323. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mco/1.0/constraints.xsd +188 -0
  324. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mco/1.0/mco.xsd +8 -0
  325. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/md1/1.0/md1.xsd +9 -0
  326. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/md1/1.0/metadataWExtendedType.xsd +4 -0
  327. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/md2/1.0/md2.xsd +15 -0
  328. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/md2/1.0/metadataWithExtensions.xsd +4 -0
  329. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mda/1.0/mda.xsd +8 -0
  330. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mda/1.0/metadataApplication.xsd +200 -0
  331. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mdb/1.0/mdb.xsd +14 -0
  332. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mdb/1.0/metadataBase.xsd +98 -0
  333. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mdb/2.0/mdb.xsd +20 -0
  334. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mdb/2.0/metadataBase.xsd +102 -0
  335. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mds/1.0/mds.xsd +22 -0
  336. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mds/1.0/metadataDataServices.xsd +4 -0
  337. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mdt/1.0/mdt.xsd +13 -0
  338. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mdt/1.0/metadataTransfer.xsd +111 -0
  339. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mex/1.0/metadataExtension.xsd +156 -0
  340. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mex/1.0/mex.xsd +8 -0
  341. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mmi/1.0/maintenance.xsd +76 -0
  342. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mmi/1.0/mmi.xsd +8 -0
  343. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mpc/1.0/mpc.xsd +8 -0
  344. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mpc/1.0/portrayalCatalogue.xsd +31 -0
  345. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mrc/1.0/content.xsd +416 -0
  346. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mrc/1.0/contentInformationImagery.xsd +185 -0
  347. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mrc/1.0/mrc.xsd +11 -0
  348. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mrc/2.0/content.xsd +419 -0
  349. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mrc/2.0/contentInformationImagery.xsd +171 -0
  350. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mrc/2.0/mrc.xsd +11 -0
  351. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mrd/1.0/distribution.xsd +267 -0
  352. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mrd/1.0/mrd.xsd +8 -0
  353. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mri/1.0/identification.xsd +517 -0
  354. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mri/1.0/mri.xsd +9 -0
  355. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mrl/1.0/lineage.xsd +147 -0
  356. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mrl/1.0/lineageImagery.xsd +236 -0
  357. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mrl/1.0/mrl.xsd +9 -0
  358. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mrl/2.0/lineage.xsd +147 -0
  359. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mrl/2.0/lineageImagery.xsd +312 -0
  360. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mrl/2.0/mrl.xsd +11 -0
  361. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mrs/1.0/mrs.xsd +8 -0
  362. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mrs/1.0/referenceSystem.xsd +47 -0
  363. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/msr/1.0/msr.xsd +10 -0
  364. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/msr/1.0/spatialRepresentation.xsd +375 -0
  365. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/msr/1.0/spatialRepresentationImagery.xsd +119 -0
  366. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/msr/2.0/msr.xsd +15 -0
  367. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/msr/2.0/spatialRepresentation.xsd +381 -0
  368. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/msr/2.0/spatialRepresentationImagery.xsd +120 -0
  369. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/srv/2.0/serviceInformation.xsd +272 -0
  370. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/srv/2.0/srv.xsd +6 -0
  371. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/srv/2.1/serviceInformation.xsd +278 -0
  372. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/srv/2.1/srv.xsd +6 -0
  373. pycsw/plugins/profiles/profile.py +141 -0
  374. pycsw/plugins/repository/__init__.py +29 -0
  375. pycsw/plugins/repository/odc/__init__.py +29 -0
  376. pycsw/plugins/repository/odc/odc.py +153 -0
  377. pycsw/server.py +938 -0
  378. pycsw/sru.py +217 -0
  379. pycsw/stac/__init__.py +29 -0
  380. pycsw/stac/api.py +571 -0
  381. pycsw/wsgi.py +233 -0
  382. pycsw/wsgi_flask.py +345 -0
pycsw/ogc/csw/csw3.py ADDED
@@ -0,0 +1,2193 @@
1
+ # -*- coding: utf-8 -*-
2
+ # =================================================================
3
+ #
4
+ # Authors: Tom Kralidis <tomkralidis@gmail.com>
5
+ #
6
+ # Copyright (c) 2024 Tom Kralidis
7
+ #
8
+ # Permission is hereby granted, free of charge, to any person
9
+ # obtaining a copy of this software and associated documentation
10
+ # files (the "Software"), to deal in the Software without
11
+ # restriction, including without limitation the rights to use,
12
+ # copy, modify, merge, publish, distribute, sublicense, and/or sell
13
+ # copies of the Software, and to permit persons to whom the
14
+ # Software is furnished to do so, subject to the following
15
+ # conditions:
16
+ #
17
+ # The above copyright notice and this permission notice shall be
18
+ # included in all copies or substantial portions of the Software.
19
+ #
20
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
21
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
22
+ # OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
23
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
24
+ # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
25
+ # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
26
+ # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
27
+ # OTHER DEALINGS IN THE SOFTWARE.
28
+ #
29
+ # =================================================================
30
+
31
+ import os
32
+ from time import time
33
+ from pycsw.core.etree import etree
34
+ from pycsw.ogc.csw.cql import cql2fes
35
+ from pycsw import opensearch
36
+ from pycsw.core import metadata, util
37
+ from pycsw.core.formats.fmt_json import xml2dict
38
+ from pycsw.ogc.fes import fes1, fes2
39
+ import logging
40
+
41
+ LOGGER = logging.getLogger(__name__)
42
+
43
+
44
+ class Csw3(object):
45
+ ''' CSW 3.x server '''
46
+ def __init__(self, server_csw):
47
+ ''' Initialize CSW3 '''
48
+
49
+ self.parent = server_csw
50
+ self.version = '3.0.0'
51
+
52
+ def getcapabilities(self):
53
+ ''' Handle GetCapabilities request '''
54
+ serviceidentification = True
55
+ serviceprovider = True
56
+ operationsmetadata = True
57
+ filtercaps = False
58
+ languages = False
59
+
60
+ # validate acceptformats
61
+ LOGGER.info('Validating ows20:AcceptFormats')
62
+ LOGGER.debug(self.parent.context.model['operations']['GetCapabilities']['parameters']['acceptFormats']['values'])
63
+ if 'acceptformats' in self.parent.kvp:
64
+ bfound = False
65
+ for fmt in self.parent.kvp['acceptformats'].split(','):
66
+ if fmt in self.parent.context.model['operations']['GetCapabilities']['parameters']['acceptFormats']['values']:
67
+ self.parent.mimetype = fmt
68
+ bfound = True
69
+ break
70
+ if not bfound:
71
+ return self.exceptionreport('InvalidParameterValue',
72
+ 'acceptformats', 'Invalid acceptFormats parameter value: %s' %
73
+ self.parent.kvp['acceptformats'])
74
+
75
+ if 'sections' in self.parent.kvp and self.parent.kvp['sections'] != '':
76
+ serviceidentification = False
77
+ serviceprovider = False
78
+ operationsmetadata = False
79
+ for section in self.parent.kvp['sections'].split(','):
80
+ if section == 'ServiceIdentification':
81
+ serviceidentification = True
82
+ if section == 'ServiceProvider':
83
+ serviceprovider = True
84
+ if section == 'OperationsMetadata':
85
+ operationsmetadata = True
86
+ if section == 'All':
87
+ serviceidentification = True
88
+ serviceprovider = True
89
+ operationsmetadata = True
90
+ filtercaps = True
91
+ languages = True
92
+ else:
93
+ filtercaps = True
94
+ languages = True
95
+
96
+ # check extra parameters that may be def'd by profiles
97
+ if self.parent.profiles is not None:
98
+ for prof in self.parent.profiles['loaded'].keys():
99
+ result = \
100
+ self.parent.profiles['loaded'][prof].check_parameters(self.parent.kvp)
101
+ if result is not None:
102
+ return self.exceptionreport(result['code'],
103
+ result['locator'], result['text'])
104
+
105
+ # @updateSequence: get latest update to repository
106
+ try:
107
+ updatesequence = \
108
+ util.get_time_iso2unix(self.parent.repository.query_insert())
109
+ except Exception as err:
110
+ LOGGER.debug(f'Cannot set updatesequence: {err}')
111
+ updatesequence = None
112
+
113
+ node = etree.Element(util.nspath_eval('csw30:Capabilities',
114
+ self.parent.context.namespaces),
115
+ nsmap=self.parent.context.namespaces, version='3.0.0',
116
+ updateSequence=str(updatesequence))
117
+
118
+ if 'updatesequence' in self.parent.kvp:
119
+ if int(self.parent.kvp['updatesequence']) == updatesequence:
120
+ return node
121
+ elif int(self.parent.kvp['updatesequence']) > updatesequence:
122
+ return self.exceptionreport('InvalidUpdateSequence',
123
+ 'updatesequence',
124
+ 'outputsequence specified (%s) is higher than server\'s \
125
+ updatesequence (%s)' % (self.parent.kvp['updatesequence'],
126
+ updatesequence))
127
+
128
+ node.attrib[util.nspath_eval('xsi:schemaLocation',
129
+ self.parent.context.namespaces)] = '%s %s/cat/csw/3.0/cswGetCapabilities.xsd' % \
130
+ (self.parent.context.namespaces['csw30'],
131
+ self.parent.config['server'].get('ogc_schemas_base'))
132
+
133
+ metadata_main = self.parent.config['metadata']
134
+
135
+ if serviceidentification:
136
+ LOGGER.info('Writing section ServiceIdentification')
137
+
138
+ serviceidentification = etree.SubElement(node, \
139
+ util.nspath_eval('ows20:ServiceIdentification',
140
+ self.parent.context.namespaces))
141
+
142
+ etree.SubElement(serviceidentification,
143
+ util.nspath_eval('ows20:Title', self.parent.context.namespaces)).text = \
144
+ metadata_main['identification'].get('title', 'missing')
145
+
146
+ etree.SubElement(serviceidentification,
147
+ util.nspath_eval('ows20:Abstract', self.parent.context.namespaces)).text = \
148
+ metadata_main['identification'].get('description', 'missing')
149
+
150
+ keywords = etree.SubElement(serviceidentification,
151
+ util.nspath_eval('ows20:Keywords', self.parent.context.namespaces))
152
+
153
+ for k in metadata_main['identification']['keywords']:
154
+ etree.SubElement(
155
+ keywords, util.nspath_eval('ows20:Keyword',
156
+ self.parent.context.namespaces)).text = k
157
+
158
+ etree.SubElement(keywords,
159
+ util.nspath_eval('ows20:Type', self.parent.context.namespaces),
160
+ codeSpace='ISOTC211/19115').text = \
161
+ metadata_main['identification'].get('keywords_type', 'missing')
162
+
163
+ etree.SubElement(serviceidentification,
164
+ util.nspath_eval('ows20:ServiceType', self.parent.context.namespaces),
165
+ codeSpace='OGC').text = 'CSW'
166
+
167
+ for stv in self.parent.context.model['parameters']['version']['values']:
168
+ etree.SubElement(serviceidentification,
169
+ util.nspath_eval('ows20:ServiceTypeVersion',
170
+ self.parent.context.namespaces)).text = stv
171
+
172
+ if self.parent.profiles is not None:
173
+ for prof in self.parent.profiles['loaded'].keys():
174
+ prof_name = self.parent.profiles['loaded'][prof].name
175
+ prof_val = self.parent.profiles['loaded'][prof].namespaces[prof_name]
176
+
177
+ etree.SubElement(serviceidentification,
178
+ util.nspath_eval('ows20:Profile',
179
+ self.parent.context.namespaces)).text = prof_val
180
+
181
+ etree.SubElement(serviceidentification,
182
+ util.nspath_eval('ows20:Fees', self.parent.context.namespaces)).text = \
183
+ metadata_main['identification'].get('fees', 'missing')
184
+
185
+ etree.SubElement(serviceidentification,
186
+ util.nspath_eval('ows20:AccessConstraints',
187
+ self.parent.context.namespaces)).text = \
188
+ metadata_main['identification'].get('accessconstraints', 'missing')
189
+
190
+ if serviceprovider:
191
+ LOGGER.info('Writing section ServiceProvider')
192
+ serviceprovider = etree.SubElement(node,
193
+ util.nspath_eval('ows20:ServiceProvider', self.parent.context.namespaces))
194
+
195
+ etree.SubElement(serviceprovider,
196
+ util.nspath_eval('ows20:ProviderName', self.parent.context.namespaces)).text = \
197
+ metadata_main['provider'].get('name', 'missing')
198
+
199
+ providersite = etree.SubElement(serviceprovider,
200
+ util.nspath_eval('ows20:ProviderSite', self.parent.context.namespaces))
201
+
202
+ providersite.attrib[util.nspath_eval('xlink:type',
203
+ self.parent.context.namespaces)] = 'simple'
204
+
205
+ providersite.attrib[util.nspath_eval('xlink:href',
206
+ self.parent.context.namespaces)] = \
207
+ metadata_main['provider'].get('url', 'missing')
208
+
209
+ servicecontact = etree.SubElement(serviceprovider,
210
+ util.nspath_eval('ows20:ServiceContact', self.parent.context.namespaces))
211
+
212
+ etree.SubElement(servicecontact,
213
+ util.nspath_eval('ows20:IndividualName',
214
+ self.parent.context.namespaces)).text = \
215
+ metadata_main['contact'].get('name', 'missing')
216
+
217
+ etree.SubElement(servicecontact,
218
+ util.nspath_eval('ows20:PositionName',
219
+ self.parent.context.namespaces)).text = \
220
+ metadata_main['contact'].get('position', 'missing')
221
+
222
+ contactinfo = etree.SubElement(servicecontact,
223
+ util.nspath_eval('ows20:ContactInfo', self.parent.context.namespaces))
224
+
225
+ phone = etree.SubElement(contactinfo, util.nspath_eval('ows20:Phone',
226
+ self.parent.context.namespaces))
227
+
228
+ etree.SubElement(phone, util.nspath_eval('ows20:Voice',
229
+ self.parent.context.namespaces)).text = \
230
+ metadata_main['contact'].get('phone', 'missing')
231
+
232
+ etree.SubElement(phone, util.nspath_eval('ows20:Facsimile',
233
+ self.parent.context.namespaces)).text = \
234
+ metadata_main['contact'].get('fax', 'missing')
235
+
236
+ address = etree.SubElement(contactinfo,
237
+ util.nspath_eval('ows20:Address', self.parent.context.namespaces))
238
+
239
+ etree.SubElement(address,
240
+ util.nspath_eval('ows20:DeliveryPoint',
241
+ self.parent.context.namespaces)).text = \
242
+ metadata_main['contact'].get('address', 'missing')
243
+
244
+ etree.SubElement(address, util.nspath_eval('ows20:City',
245
+ self.parent.context.namespaces)).text = \
246
+ metadata_main['contact'].get('city', 'missing')
247
+
248
+ etree.SubElement(address,
249
+ util.nspath_eval('ows20:AdministrativeArea',
250
+ self.parent.context.namespaces)).text = \
251
+ metadata_main['contact'].get('stateorprovince', 'missing')
252
+
253
+ etree.SubElement(address,
254
+ util.nspath_eval('ows20:PostalCode',
255
+ self.parent.context.namespaces)).text = \
256
+ str(metadata_main['contact'].get('postalcode', 'missing'))
257
+
258
+ etree.SubElement(address,
259
+ util.nspath_eval('ows20:Country', self.parent.context.namespaces)).text = \
260
+ metadata_main['contact'].get('country', 'missing')
261
+
262
+ etree.SubElement(address,
263
+ util.nspath_eval('ows20:ElectronicMailAddress',
264
+ self.parent.context.namespaces)).text = \
265
+ metadata_main['contact'].get('email', 'missing')
266
+
267
+ url = etree.SubElement(contactinfo,
268
+ util.nspath_eval('ows20:OnlineResource', self.parent.context.namespaces))
269
+
270
+ url.attrib[util.nspath_eval('xlink:type',
271
+ self.parent.context.namespaces)] = 'simple'
272
+
273
+ url.attrib[util.nspath_eval('xlink:href',
274
+ self.parent.context.namespaces)] = \
275
+ metadata_main['contact'].get('url', 'missing')
276
+
277
+ etree.SubElement(contactinfo,
278
+ util.nspath_eval('ows20:HoursOfService',
279
+ self.parent.context.namespaces)).text = \
280
+ metadata_main['contact'].get('hours', 'missing')
281
+
282
+ etree.SubElement(contactinfo,
283
+ util.nspath_eval('ows20:ContactInstructions',
284
+ self.parent.context.namespaces)).text = \
285
+ metadata_main['contact'].get('instructions', 'missing')
286
+
287
+ etree.SubElement(servicecontact,
288
+ util.nspath_eval('ows20:Role', self.parent.context.namespaces),
289
+ codeSpace='ISOTC211/19115').text = \
290
+ metadata_main['contact'].get('role', 'missing')
291
+
292
+ if operationsmetadata:
293
+ LOGGER.info('Writing section OperationsMetadata')
294
+ operationsmetadata = etree.SubElement(node,
295
+ util.nspath_eval('ows20:OperationsMetadata',
296
+ self.parent.context.namespaces))
297
+
298
+ for operation in self.parent.context.model['operations_order']:
299
+ oper = etree.SubElement(operationsmetadata,
300
+ util.nspath_eval('ows20:Operation', self.parent.context.namespaces),
301
+ name=operation)
302
+
303
+ dcp = etree.SubElement(oper, util.nspath_eval('ows20:DCP',
304
+ self.parent.context.namespaces))
305
+
306
+ http = etree.SubElement(dcp, util.nspath_eval('ows20:HTTP',
307
+ self.parent.context.namespaces))
308
+
309
+ if self.parent.context.model['operations'][operation]['methods']['get']:
310
+ get = etree.SubElement(http, util.nspath_eval('ows20:Get',
311
+ self.parent.context.namespaces))
312
+
313
+ get.attrib[util.nspath_eval('xlink:type',\
314
+ self.parent.context.namespaces)] = 'simple'
315
+
316
+ get.attrib[util.nspath_eval('xlink:href',\
317
+ self.parent.context.namespaces)] = self.parent.config['server'].get('url')
318
+
319
+ if self.parent.context.model['operations'][operation]['methods']['post']:
320
+ post = etree.SubElement(http, util.nspath_eval('ows20:Post',
321
+ self.parent.context.namespaces))
322
+ post.attrib[util.nspath_eval('xlink:type',
323
+ self.parent.context.namespaces)] = 'simple'
324
+ post.attrib[util.nspath_eval('xlink:href',
325
+ self.parent.context.namespaces)] = \
326
+ self.parent.config['server'].get('url')
327
+
328
+ for parameter in \
329
+ sorted(self.parent.context.model['operations'][operation]['parameters']):
330
+ param = etree.SubElement(oper,
331
+ util.nspath_eval('ows20:Parameter',
332
+ self.parent.context.namespaces), name=parameter)
333
+
334
+ param.append(self._write_allowed_values(self.parent.context.model['operations'][operation]['parameters'][parameter]['values']))
335
+
336
+ if operation == 'GetRecords': # advertise queryables, MaxRecordDefault
337
+ for qbl in sorted(self.parent.repository.queryables.keys()):
338
+ if qbl not in ['_all', 'SupportedDublinCoreQueryables']:
339
+ param = etree.SubElement(oper,
340
+ util.nspath_eval('ows20:Constraint',
341
+ self.parent.context.namespaces), name=qbl)
342
+
343
+ param.append(self._write_allowed_values(self.parent.repository.queryables[qbl]))
344
+
345
+ if self.parent.profiles is not None:
346
+ for con in sorted(self.parent.context.model[\
347
+ 'operations']['GetRecords']['constraints'].keys()):
348
+ param = etree.SubElement(oper,
349
+ util.nspath_eval('ows20:Constraint',
350
+ self.parent.context.namespaces), name=con)
351
+
352
+ param.append(self._write_allowed_values(self.parent.context.model['operations']['GetRecords']['constraints'][con]['values']))
353
+
354
+ extra_constraints = {
355
+ 'OpenSearchDescriptionDocument': ['%s?mode=opensearch&service=CSW&version=3.0.0&request=GetCapabilities' % self.parent.config['server'].get('url')],
356
+ 'MaxRecordDefault': self.parent.context.model['constraints']['MaxRecordDefault']['values'],
357
+ }
358
+
359
+ for key in sorted(extra_constraints.keys()):
360
+ param = etree.SubElement(oper,
361
+ util.nspath_eval('ows20:Constraint',
362
+ self.parent.context.namespaces), name=key)
363
+ param.append(self._write_allowed_values(extra_constraints[key]))
364
+
365
+ if 'FederatedCatalogues' in self.parent.context.model['constraints']:
366
+ param = etree.SubElement(oper,
367
+ util.nspath_eval('ows20:Constraint',
368
+ self.parent.context.namespaces), name='FederatedCatalogues')
369
+ param.append(self._write_allowed_values(self.parent.context.model['constraints']['FederatedCatalogues']['values']))
370
+
371
+ for parameter in sorted(self.parent.context.model['parameters'].keys()):
372
+ param = etree.SubElement(operationsmetadata,
373
+ util.nspath_eval('ows20:Parameter', self.parent.context.namespaces),
374
+ name=parameter)
375
+
376
+ param.append(self._write_allowed_values(self.parent.context.model['parameters'][parameter]['values']))
377
+
378
+ for qbl in sorted(self.parent.repository.queryables.keys()):
379
+ if qbl == 'SupportedDublinCoreQueryables':
380
+ param = etree.SubElement(operationsmetadata,
381
+ util.nspath_eval('ows20:Constraint',
382
+ self.parent.context.namespaces), name='CoreQueryables')
383
+ param.append(self._write_allowed_values(self.parent.repository.queryables[qbl]))
384
+
385
+ for constraint in sorted(self.parent.context.model['constraints'].keys()):
386
+ param = etree.SubElement(operationsmetadata,
387
+ util.nspath_eval('ows20:Constraint', self.parent.context.namespaces),
388
+ name=constraint)
389
+
390
+ param.append(self._write_allowed_values(self.parent.context.model['constraints'][constraint]['values']))
391
+
392
+ if self.parent.profiles is not None:
393
+ for prof in self.parent.profiles['loaded'].keys():
394
+ ecnode = \
395
+ self.parent.profiles['loaded'][prof].get_extendedcapabilities()
396
+ if ecnode is not None:
397
+ operationsmetadata.append(ecnode)
398
+
399
+ if languages:
400
+ LOGGER.info('Writing section ows:Languages')
401
+ langs = etree.SubElement(node,
402
+ util.nspath_eval('ows20:Languages', self.parent.context.namespaces))
403
+ etree.SubElement(langs,
404
+ util.nspath_eval('ows20:Language', self.parent.context.namespaces)).text = self.parent.language['639_code']
405
+
406
+ if not filtercaps:
407
+ return node
408
+
409
+ # always write out Filter_Capabilities
410
+ LOGGER.info('Writing section Filter_Capabilities')
411
+ fltcaps = etree.SubElement(node,
412
+ util.nspath_eval('fes20:Filter_Capabilities', self.parent.context.namespaces))
413
+
414
+ conformance = etree.SubElement(fltcaps,
415
+ util.nspath_eval('fes20:Conformance', self.parent.context.namespaces))
416
+
417
+ for value in fes2.MODEL['Conformance']['values']:
418
+ constraint = etree.SubElement(conformance,
419
+ util.nspath_eval('fes20:Constraint', self.parent.context.namespaces),
420
+ name=value)
421
+ etree.SubElement(constraint,
422
+ util.nspath_eval('ows11:NoValues', self.parent.context.namespaces))
423
+ etree.SubElement(constraint,
424
+ util.nspath_eval('ows11:DefaultValue', self.parent.context.namespaces)).text = 'TRUE'
425
+
426
+ idcaps = etree.SubElement(fltcaps,
427
+ util.nspath_eval('fes20:Id_Capabilities', self.parent.context.namespaces))
428
+
429
+ for idcap in fes2.MODEL['Ids']['values']:
430
+ etree.SubElement(idcaps, util.nspath_eval('fes20:ResourceIdentifier',
431
+ self.parent.context.namespaces), name=idcap)
432
+
433
+ scalarcaps = etree.SubElement(fltcaps,
434
+ util.nspath_eval('fes20:Scalar_Capabilities', self.parent.context.namespaces))
435
+
436
+ etree.SubElement(scalarcaps, util.nspath_eval('fes20:LogicalOperators',
437
+ self.parent.context.namespaces))
438
+
439
+ cmpops = etree.SubElement(scalarcaps,
440
+ util.nspath_eval('fes20:ComparisonOperators', self.parent.context.namespaces))
441
+
442
+ for cmpop in sorted(fes2.MODEL['ComparisonOperators'].keys()):
443
+ etree.SubElement(cmpops,
444
+ util.nspath_eval('fes20:ComparisonOperator',
445
+ self.parent.context.namespaces), name=fes2.MODEL['ComparisonOperators'][cmpop]['opname'])
446
+
447
+ spatialcaps = etree.SubElement(fltcaps,
448
+ util.nspath_eval('fes20:Spatial_Capabilities', self.parent.context.namespaces))
449
+
450
+ geomops = etree.SubElement(spatialcaps,
451
+ util.nspath_eval('fes20:GeometryOperands', self.parent.context.namespaces))
452
+
453
+ for geomtype in \
454
+ fes2.MODEL['GeometryOperands']['values']:
455
+ etree.SubElement(geomops,
456
+ util.nspath_eval('fes20:GeometryOperand',
457
+ self.parent.context.namespaces), name=geomtype)
458
+
459
+ spatialops = etree.SubElement(spatialcaps,
460
+ util.nspath_eval('fes20:SpatialOperators', self.parent.context.namespaces))
461
+
462
+ for spatial_comparison in \
463
+ fes2.MODEL['SpatialOperators']['values']:
464
+ etree.SubElement(spatialops,
465
+ util.nspath_eval('fes20:SpatialOperator', self.parent.context.namespaces),
466
+ name=spatial_comparison)
467
+
468
+ functions = etree.SubElement(fltcaps,
469
+ util.nspath_eval('fes20:Functions', self.parent.context.namespaces))
470
+
471
+ for fnop in sorted(fes2.MODEL['Functions'].keys()):
472
+ fn = etree.SubElement(functions,
473
+ util.nspath_eval('fes20:Function', self.parent.context.namespaces),
474
+ name=fnop)
475
+
476
+ etree.SubElement(fn, util.nspath_eval('fes20:Returns',
477
+ self.parent.context.namespaces)).text = \
478
+ fes2.MODEL['Functions'][fnop]['returns']
479
+
480
+ return node
481
+
482
+ def getdomain(self):
483
+ ''' Handle GetDomain request '''
484
+ if ('parametername' not in self.parent.kvp and
485
+ 'valuereference' not in self.parent.kvp):
486
+ return self.exceptionreport('MissingParameterValue',
487
+ 'parametername', 'Missing value. \
488
+ One of valuereference or parametername must be specified')
489
+
490
+ node = etree.Element(util.nspath_eval('csw30:GetDomainResponse',
491
+ self.parent.context.namespaces), nsmap=self.parent.context.namespaces)
492
+
493
+ node.attrib[util.nspath_eval('xsi:schemaLocation',
494
+ self.parent.context.namespaces)] = '%s %s/cat/csw/3.0/cswGetDomain.xsd' % \
495
+ (self.parent.context.namespaces['csw30'],
496
+ self.parent.config['server'].get('ogc_schemas_base'))
497
+
498
+ if 'parametername' in self.parent.kvp:
499
+ for pname in self.parent.kvp['parametername'].split(','):
500
+ LOGGER.info('Parsing parametername %s', pname)
501
+ domainvalue = etree.SubElement(node,
502
+ util.nspath_eval('csw30:DomainValues', self.parent.context.namespaces),
503
+ type='csw30:Record', resultType='available')
504
+ etree.SubElement(domainvalue,
505
+ util.nspath_eval('csw30:ParameterName',
506
+ self.parent.context.namespaces)).text = pname
507
+ try:
508
+ operation, parameter = pname.split('.')
509
+ except Exception as err:
510
+ LOGGER.debug(f'pname2 not found: {err}')
511
+ return node
512
+ if (operation in self.parent.context.model['operations'] and
513
+ parameter in self.parent.context.model['operations'][operation]['parameters']):
514
+ listofvalues = etree.SubElement(domainvalue,
515
+ util.nspath_eval('csw30:ListOfValues', self.parent.context.namespaces))
516
+ for val in \
517
+ sorted(self.parent.context.model['operations'][operation]\
518
+ ['parameters'][parameter]['values']):
519
+ etree.SubElement(listofvalues,
520
+ util.nspath_eval('csw30:Value',
521
+ self.parent.context.namespaces)).text = val
522
+
523
+ if 'valuereference' in self.parent.kvp:
524
+ for pname in self.parent.kvp['valuereference'].split(','):
525
+ LOGGER.info('Parsing valuereference %s', pname)
526
+
527
+ if pname.find('/') == 0: # it's an XPath
528
+ pname2 = pname
529
+ else: # it's a core queryable, map to internal typename model
530
+ try:
531
+ pname2 = self.parent.repository.queryables['_all'][pname]['dbcol']
532
+ except Exception as err:
533
+ LOGGER.debug(f'pname2 not found: {err}')
534
+
535
+ # decipher typename
536
+ dvtype = None
537
+ if self.parent.profiles is not None:
538
+ for prof in self.parent.profiles['loaded'].keys():
539
+ for prefix in self.parent.profiles['loaded'][prof].prefixes:
540
+ if pname2.find(prefix) != -1:
541
+ dvtype = self.parent.profiles['loaded'][prof].typename
542
+ break
543
+ if not dvtype:
544
+ dvtype = 'csw30:Record'
545
+
546
+ domainvalue = etree.SubElement(node,
547
+ util.nspath_eval('csw30:DomainValues', self.parent.context.namespaces),
548
+ type=dvtype, resultType='available')
549
+ etree.SubElement(domainvalue,
550
+ util.nspath_eval('csw30:ValueReference',
551
+ self.parent.context.namespaces)).text = pname
552
+
553
+ try:
554
+ LOGGER.debug(
555
+ 'Querying repository property %s, typename %s, \
556
+ domainquerytype %s',
557
+ pname2, dvtype, self.parent.domainquerytype)
558
+
559
+ results = self.parent.repository.query_domain(
560
+ pname2, dvtype, self.parent.domainquerytype, True)
561
+
562
+ LOGGER.debug('Results: %d', len(results))
563
+
564
+ if self.parent.domainquerytype == 'range':
565
+ rangeofvalues = etree.SubElement(domainvalue,
566
+ util.nspath_eval('csw30:RangeOfValues',
567
+ self.parent.context.namespaces))
568
+
569
+ etree.SubElement(rangeofvalues,
570
+ util.nspath_eval('csw30:MinValue',
571
+ self.parent.context.namespaces)).text = results[0][0]
572
+
573
+ etree.SubElement(rangeofvalues,
574
+ util.nspath_eval('csw30:MaxValue',
575
+ self.parent.context.namespaces)).text = results[0][1]
576
+ else:
577
+ listofvalues = etree.SubElement(domainvalue,
578
+ util.nspath_eval('csw30:ListOfValues',
579
+ self.parent.context.namespaces))
580
+ for result in results:
581
+ LOGGER.debug(str(result))
582
+ if (result is not None and
583
+ result[0] is not None): # drop null values
584
+ etree.SubElement(listofvalues,
585
+ util.nspath_eval('csw30:Value',
586
+ self.parent.context.namespaces),
587
+ count=str(result[1])).text = result[0]
588
+ except Exception as err:
589
+ # here we fail silently back to the client because
590
+ # CSW tells us to
591
+ LOGGER.exception('No results for propertyname')
592
+ return node
593
+
594
+ def getrecords(self):
595
+ ''' Handle GetRecords request '''
596
+
597
+ timestamp = util.get_today_and_now()
598
+
599
+ if ('elementsetname' not in self.parent.kvp and
600
+ 'elementname' not in self.parent.kvp):
601
+ if self.parent.requesttype == 'GET':
602
+ LOGGER.debug(self.parent.requesttype)
603
+ self.parent.kvp['elementsetname'] = 'summary'
604
+ else:
605
+ # mutually exclusive required
606
+ return self.exceptionreport('MissingParameterValue',
607
+ 'elementsetname',
608
+ 'Missing one of ElementSetName or ElementName parameter(s)')
609
+
610
+ if 'elementsetname' in self.parent.kvp and 'elementname' in self.parent.kvp and self.parent.kvp['elementname']:
611
+ # mutually exclusive required
612
+ return self.exceptionreport('NoApplicableCode',
613
+ 'elementsetname',
614
+ 'Only ONE of ElementSetName or ElementName parameter(s) is permitted')
615
+
616
+ if 'elementsetname' not in self.parent.kvp:
617
+ self.parent.kvp['elementsetname'] = 'summary'
618
+
619
+ if 'outputschema' not in self.parent.kvp:
620
+ self.parent.kvp['outputschema'] = self.parent.context.namespaces['csw30']
621
+
622
+ LOGGER.debug(self.parent.context.model['operations']['GetRecords']['parameters']['outputSchema']['values'])
623
+ if (self.parent.kvp['outputschema'] not in self.parent.context.model['operations']
624
+ ['GetRecords']['parameters']['outputSchema']['values']):
625
+ return self.exceptionreport('InvalidParameterValue',
626
+ 'outputschema', 'Invalid outputSchema parameter value: %s' %
627
+ self.parent.kvp['outputschema'])
628
+
629
+ if 'outputformat' not in self.parent.kvp:
630
+ self.parent.kvp['outputformat'] = 'application/xml'
631
+
632
+ if 'HTTP_ACCEPT' in self.parent.environ:
633
+ LOGGER.debug('Detected HTTP Accept header: %s', self.parent.environ['HTTP_ACCEPT'])
634
+ formats_match = False
635
+ if 'outputformat' in self.parent.kvp:
636
+ LOGGER.debug(self.parent.kvp['outputformat'])
637
+ for ofmt in self.parent.environ['HTTP_ACCEPT'].split(','):
638
+ LOGGER.info('Comparing %s and %s', ofmt, self.parent.kvp['outputformat'])
639
+ if ofmt.split('/')[0] in self.parent.kvp['outputformat']:
640
+ LOGGER.debug('Found output match')
641
+ formats_match = True
642
+ if not formats_match and self.parent.environ['HTTP_ACCEPT'] != '*/*':
643
+ return self.exceptionreport('InvalidParameterValue',
644
+ 'outputformat', 'HTTP Accept header (%s) and outputformat (%s) must agree' %
645
+ (self.parent.environ['HTTP_ACCEPT'], self.parent.kvp['outputformat']))
646
+ else:
647
+ for ofmt in self.parent.environ['HTTP_ACCEPT'].split(','):
648
+ if ofmt in self.parent.context.model['operations']['GetRecords']['parameters']['outputFormat']['values']:
649
+ self.parent.kvp['outputformat'] = ofmt
650
+ break
651
+
652
+
653
+ if (self.parent.kvp['outputformat'] not in self.parent.context.model['operations']
654
+ ['GetRecords']['parameters']['outputFormat']['values']):
655
+ return self.exceptionreport('InvalidParameterValue',
656
+ 'outputformat', 'Invalid outputFormat parameter value: %s' %
657
+ self.parent.kvp['outputformat'])
658
+
659
+ if 'outputformat' in self.parent.kvp:
660
+ LOGGER.info('Setting content type')
661
+ self.parent.contenttype = self.parent.kvp['outputformat']
662
+ if self.parent.kvp['outputformat'] == 'application/atom+xml':
663
+ self.parent.kvp['outputschema'] = self.parent.context.namespaces['atom']
664
+ self.parent.mode = 'opensearch'
665
+
666
+ if (('elementname' not in self.parent.kvp or
667
+ len(self.parent.kvp['elementname']) == 0) and
668
+ self.parent.kvp['elementsetname'] not in
669
+ self.parent.context.model['operations']['GetRecords']['parameters']
670
+ ['ElementSetName']['values']):
671
+ return self.exceptionreport('InvalidParameterValue',
672
+ 'elementsetname', 'Invalid ElementSetName parameter value: %s' %
673
+ self.parent.kvp['elementsetname'])
674
+
675
+ if 'typenames' not in self.parent.kvp:
676
+ return self.exceptionreport('MissingParameterValue',
677
+ 'typenames', 'Missing typenames parameter')
678
+
679
+ if ('typenames' in self.parent.kvp and
680
+ self.parent.requesttype == 'GET'): # passed via GET
681
+ #self.parent.kvp['typenames'] = self.parent.kvp['typenames'].split(',')
682
+ self.parent.kvp['typenames'] = ['csw:Record' if x=='Record' else x for x in self.parent.kvp['typenames'].split(',')]
683
+
684
+ if 'namespace' in self.parent.kvp:
685
+ LOGGER.info('resolving KVP namespace bindings')
686
+ LOGGER.debug(self.parent.kvp['typenames'])
687
+ self.parent.kvp['typenames'] = self.resolve_nsmap(self.parent.kvp['typenames'])
688
+ if 'elementname' in self.parent.kvp:
689
+ LOGGER.debug(self.parent.kvp['elementname'])
690
+ self.parent.kvp['elementname'] = self.resolve_nsmap(self.parent.kvp['elementname'].split(','))
691
+
692
+ if 'typenames' in self.parent.kvp:
693
+ for tname in self.parent.kvp['typenames']:
694
+ #if tname == 'Record':
695
+ # tname = 'csw:Record'
696
+ if (tname not in self.parent.context.model['operations']['GetRecords']
697
+ ['parameters']['typeNames']['values']):
698
+ return self.exceptionreport('InvalidParameterValue',
699
+ 'typenames', 'Invalid typeNames parameter value: %s' %
700
+ tname)
701
+
702
+ # check elementname's
703
+ if 'elementname' in self.parent.kvp:
704
+ for ename in self.parent.kvp['elementname']:
705
+ if ename not in self.parent.repository.queryables['_all']:
706
+ return self.exceptionreport('InvalidParameterValue',
707
+ 'elementname', 'Invalid ElementName parameter value: %s' %
708
+ ename)
709
+
710
+ maxrecords_cfg = int(self.parent.config['server'].get('maxrecords', -1))
711
+
712
+ if 'maxrecords' in self.parent.kvp and self.parent.kvp['maxrecords'] == 'unlimited':
713
+ LOGGER.debug('Detected maxrecords=unlimited')
714
+ self.parent.kvp.pop('maxrecords')
715
+
716
+ if 'maxrecords' not in self.parent.kvp: # not specified by client
717
+ if maxrecords_cfg > -1: # specified in config
718
+ self.parent.kvp['maxrecords'] = maxrecords_cfg
719
+ else: # spec default
720
+ self.parent.kvp['maxrecords'] = 10
721
+ else: # specified by client
722
+ if self.parent.kvp['maxrecords'] == '':
723
+ self.parent.kvp['maxrecords'] = 10
724
+ if maxrecords_cfg > -1: # set in config
725
+ if int(self.parent.kvp['maxrecords']) > maxrecords_cfg:
726
+ self.parent.kvp['maxrecords'] = maxrecords_cfg
727
+
728
+ if any(x in opensearch.QUERY_PARAMETERS for x in self.parent.kvp):
729
+ LOGGER.debug('OpenSearch Geo/Time parameters detected.')
730
+ self.parent.kvp['constraintlanguage'] = 'FILTER'
731
+ try:
732
+ tmp_filter = opensearch.kvp2filterxml(self.parent.kvp, self.parent.context,
733
+ self.parent.profiles, fes_version='2.0')
734
+ except Exception as err:
735
+ return self.exceptionreport('InvalidParameterValue', 'bbox', str(err))
736
+
737
+ if tmp_filter != "":
738
+ self.parent.kvp['constraint'] = tmp_filter
739
+ LOGGER.debug('OpenSearch Geo/Time parameters to Filter: %s.', self.parent.kvp['constraint'])
740
+
741
+ if self.parent.requesttype == 'GET':
742
+ if 'constraint' in self.parent.kvp:
743
+ # GET request
744
+ LOGGER.debug('csw:Constraint passed over HTTP GET.')
745
+ if 'constraintlanguage' not in self.parent.kvp:
746
+ return self.exceptionreport('MissingParameterValue',
747
+ 'constraintlanguage',
748
+ 'constraintlanguage required when constraint specified')
749
+ if (self.parent.kvp['constraintlanguage'] not in
750
+ self.parent.context.model['operations']['GetRecords']['parameters']
751
+ ['CONSTRAINTLANGUAGE']['values']):
752
+ return self.exceptionreport('InvalidParameterValue',
753
+ 'constraintlanguage', 'Invalid constraintlanguage: %s'
754
+ % self.parent.kvp['constraintlanguage'])
755
+ if self.parent.kvp['constraintlanguage'] == 'CQL_TEXT':
756
+ tmp = self.parent.kvp['constraint']
757
+ try:
758
+ LOGGER.info('Transforming CQL into fes1')
759
+ LOGGER.debug('CQL: %s', tmp)
760
+ self.parent.kvp['constraint'] = {}
761
+ self.parent.kvp['constraint']['type'] = 'filter'
762
+ cql = cql2fes(tmp, self.parent.context.namespaces, fes_version='1.0')
763
+ self.parent.kvp['constraint']['where'], self.parent.kvp['constraint']['values'] = fes1.parse(cql,
764
+ self.parent.repository.queryables['_all'], self.parent.repository.dbtype,
765
+ self.parent.context.namespaces, self.parent.orm, self.parent.language['text'], self.parent.repository.fts)
766
+ self.parent.kvp['constraint']['_dict'] = xml2dict(etree.tostring(cql), self.parent.context.namespaces)
767
+ except Exception as err:
768
+ LOGGER.exception('Invalid CQL query %s', tmp)
769
+ return self.exceptionreport('InvalidParameterValue',
770
+ 'constraint', 'Invalid Filter syntax')
771
+ elif self.parent.kvp['constraintlanguage'] == 'FILTER':
772
+ # validate filter XML
773
+ try:
774
+ schema = os.path.join(self.parent.config['server'].get('home'),
775
+ 'core', 'schemas', 'ogc', 'filter', '2.0', '_wrapper.xsd')
776
+ LOGGER.info('Validating Filter %s.', self.parent.kvp['constraint'])
777
+ schema = etree.XMLSchema(file=schema)
778
+ parser = etree.XMLParser(schema=schema, resolve_entities=False)
779
+ doc = etree.fromstring(self.parent.kvp['constraint'], parser)
780
+ LOGGER.debug('Filter is valid XML.')
781
+ self.parent.kvp['constraint'] = {}
782
+ self.parent.kvp['constraint']['type'] = 'filter'
783
+ self.parent.kvp['constraint']['where'], self.parent.kvp['constraint']['values'] = \
784
+ fes2.parse(doc,
785
+ self.parent.repository.queryables['_all'],
786
+ self.parent.repository.dbtype,
787
+ self.parent.context.namespaces, self.parent.orm, self.parent.language['text'], self.parent.repository.fts)
788
+ self.parent.kvp['constraint']['_dict'] = xml2dict(etree.tostring(doc), self.parent.context.namespaces)
789
+ except Exception as err:
790
+ errortext = \
791
+ 'Exception: document not valid.\nError: %s' % str(err)
792
+
793
+ LOGGER.exception(errortext)
794
+ return self.exceptionreport('InvalidParameterValue',
795
+ 'bbox', 'Invalid Filter query: %s' % errortext)
796
+ else:
797
+ self.parent.kvp['constraint'] = {}
798
+
799
+ if 'sortby' not in self.parent.kvp:
800
+ self.parent.kvp['sortby'] = None
801
+ elif 'sortby' in self.parent.kvp and self.parent.requesttype == 'GET':
802
+ LOGGER.debug('Sorted query specified')
803
+ tmp = self.parent.kvp['sortby']
804
+ self.parent.kvp['sortby'] = {}
805
+
806
+ try:
807
+ name, order = tmp.rsplit(':', 1)
808
+ except Exception:
809
+ return self.exceptionreport('InvalidParameterValue',
810
+ 'sortby', 'Invalid SortBy value: must be in the format\
811
+ propertyname:A or propertyname:D')
812
+
813
+ try:
814
+ self.parent.kvp['sortby']['propertyname'] = \
815
+ self.parent.repository.queryables['_all'][name]['dbcol']
816
+ if name.find('BoundingBox') != -1 or name.find('Envelope') != -1:
817
+ # it's a spatial sort
818
+ self.parent.kvp['sortby']['spatial'] = True
819
+ except Exception as err:
820
+ return self.exceptionreport('InvalidParameterValue',
821
+ 'sortby', 'Invalid SortBy propertyname: %s' % name)
822
+
823
+ if order not in ['A', 'D']:
824
+ return self.exceptionreport('InvalidParameterValue',
825
+ 'sortby', 'Invalid SortBy value: sort order must be "A" or "D"')
826
+
827
+ if order == 'D':
828
+ self.parent.kvp['sortby']['order'] = 'DESC'
829
+ else:
830
+ self.parent.kvp['sortby']['order'] = 'ASC'
831
+
832
+ if 'startposition' not in self.parent.kvp or not self.parent.kvp['startposition']:
833
+ self.parent.kvp['startposition'] = 1
834
+
835
+ if 'recordids' in self.parent.kvp and self.parent.kvp['recordids'] != '':
836
+ # query repository
837
+ LOGGER.info('Querying repository with RECORD ids: %s', self.parent.kvp['recordids'])
838
+ results = self.parent.repository.query_ids(self.parent.kvp['recordids'].split(','))
839
+ matched = str(len(results))
840
+ if len(results) == 0:
841
+ return self.exceptionreport('NotFound', 'recordids',
842
+ 'No records found for \'%s\'' % self.parent.kvp['recordids'])
843
+ else:
844
+ # query repository
845
+ LOGGER.info('Querying repository with constraint: %s,\
846
+ sortby: %s, typenames: %s, maxrecords: %s, startposition: %s.',
847
+ self.parent.kvp['constraint'], self.parent.kvp['sortby'], self.parent.kvp['typenames'],
848
+ self.parent.kvp['maxrecords'], self.parent.kvp['startposition'])
849
+
850
+ try:
851
+ matched, results = self.parent.repository.query(
852
+ constraint=self.parent.kvp['constraint'],
853
+ sortby=self.parent.kvp['sortby'], typenames=self.parent.kvp['typenames'],
854
+ maxrecords=self.parent.kvp['maxrecords'],
855
+ startposition=int(self.parent.kvp['startposition'])-1)
856
+ except Exception as err:
857
+ LOGGER.exception('Invalid query syntax. Query: %s', self.parent.kvp['constraint'])
858
+ LOGGER.exception('Invalid query syntax. Result: %s', err)
859
+ return self.exceptionreport('InvalidParameterValue', 'constraint',
860
+ 'Invalid query syntax')
861
+
862
+ if int(matched) == 0:
863
+ returned = nextrecord = '0'
864
+ elif int(self.parent.kvp['maxrecords']) == 0:
865
+ returned = nextrecord = '0'
866
+ elif int(matched) < int(self.parent.kvp['startposition']):
867
+ returned = nextrecord = '0'
868
+ elif int(matched) <= int(self.parent.kvp['startposition']) + int(self.parent.kvp['maxrecords']) - 1:
869
+ returned = str(int(matched) - int(self.parent.kvp['startposition']) + 1)
870
+ nextrecord = '0'
871
+ else:
872
+ returned = str(self.parent.kvp['maxrecords'])
873
+ nextrecord = str(int(self.parent.kvp['startposition']) + int(self.parent.kvp['maxrecords']))
874
+
875
+ LOGGER.debug('Results: matched: %s, returned: %s, next: %s',
876
+ matched, returned, nextrecord)
877
+
878
+ node = etree.Element(util.nspath_eval('csw30:GetRecordsResponse',
879
+ self.parent.context.namespaces),
880
+ nsmap=self.parent.context.namespaces, version='3.0.0')
881
+
882
+ node.attrib[util.nspath_eval('xsi:schemaLocation',
883
+ self.parent.context.namespaces)] = \
884
+ '%s %s/cat/csw/3.0/cswGetRecords.xsd' % \
885
+ (self.parent.context.namespaces['csw30'], self.parent.config['server'].get('ogc_schemas_base'))
886
+
887
+ if 'requestid' in self.parent.kvp and self.parent.kvp['requestid'] is not None:
888
+ etree.SubElement(node, util.nspath_eval('csw:RequestId',
889
+ self.parent.context.namespaces)).text = self.parent.kvp['requestid']
890
+
891
+ etree.SubElement(node, util.nspath_eval('csw30:SearchStatus',
892
+ self.parent.context.namespaces), timestamp=timestamp)
893
+
894
+ #if 'where' not in self.parent.kvp['constraint'] and \
895
+ #self.parent.kvp['resulttype'] is None:
896
+ # returned = '0'
897
+
898
+ searchresults = etree.SubElement(node,
899
+ util.nspath_eval('csw30:SearchResults', self.parent.context.namespaces),
900
+ numberOfRecordsMatched=matched, numberOfRecordsReturned=returned,
901
+ nextRecord=nextrecord, recordSchema=self.parent.kvp['outputschema'],
902
+ expires=timestamp, status=get_resultset_status(matched, nextrecord))
903
+
904
+ if self.parent.kvp['elementsetname'] is not None:
905
+ searchresults.attrib['elementSet'] = self.parent.kvp['elementsetname']
906
+
907
+ #if 'where' not in self.parent.kvp['constraint'] \
908
+ #and self.parent.kvp['resulttype'] is None:
909
+ # LOGGER.debug('Empty result set returned')
910
+ # return node
911
+
912
+ if results is not None:
913
+ if len(results) < int(self.parent.kvp['maxrecords']):
914
+ max1 = len(results)
915
+ else:
916
+ max1 = int(self.parent.kvp['startposition']) + (int(self.parent.kvp['maxrecords'])-1)
917
+ LOGGER.info('Presenting records %s - %s',
918
+ self.parent.kvp['startposition'], max1)
919
+
920
+ for res in results:
921
+ node_ = None
922
+ if self.parent.xslts:
923
+ try:
924
+ node_ = self.parent._render_xslt(res)
925
+ except Exception as err:
926
+ self.parent.response = self.exceptionreport(
927
+ 'NoApplicableCode', 'service',
928
+ 'XSLT transformation failed. Check server logs for errors')
929
+ return self.parent.response
930
+ if node_ is not None:
931
+ searchresults.append(node_)
932
+ else:
933
+ try:
934
+ if (self.parent.kvp['outputschema'] ==
935
+ 'http://www.opengis.net/cat/csw/3.0' and
936
+ ('csw:Record' in self.parent.kvp['typenames'] or
937
+ 'csw30:Record' in self.parent.kvp['typenames'])):
938
+ # serialize csw:Record inline
939
+ searchresults.append(self._write_record(
940
+ res, self.parent.repository.queryables['_all']))
941
+ elif (self.parent.kvp['outputschema'] ==
942
+ 'http://www.opengis.net/cat/csw/3.0' and
943
+ 'csw:Record' not in self.parent.kvp['typenames']):
944
+ # serialize into csw:Record model
945
+
946
+ for prof in self.parent.profiles['loaded']:
947
+ # find source typename
948
+ if self.parent.profiles['loaded'][prof].typename in \
949
+ self.parent.kvp['typenames']:
950
+ typename = self.parent.profiles['loaded'][prof].typename
951
+ break
952
+
953
+ util.transform_mappings(
954
+ self.parent.repository.queryables['_all'],
955
+ self.parent.context.model['typenames'][typename][
956
+ 'mappings']['csw:Record']
957
+ )
958
+
959
+ searchresults.append(self._write_record(
960
+ res, self.parent.repository.queryables['_all']))
961
+ elif self.parent.kvp['outputschema'] in self.parent.outputschemas: # use outputschema serializer
962
+ searchresults.append(self.parent.outputschemas[self.parent.kvp['outputschema']].write_record(res, self.parent.kvp['elementsetname'], self.parent.context, self.parent.config['server'].get('url')))
963
+ else: # use profile serializer
964
+ searchresults.append(
965
+ self.parent.profiles['loaded'][self.parent.kvp['outputschema']].\
966
+ write_record(res, self.parent.kvp['elementsetname'],
967
+ self.parent.kvp['outputschema'],
968
+ self.parent.repository.queryables['_all']))
969
+ except Exception as err:
970
+ self.parent.response = self.exceptionreport(
971
+ 'NoApplicableCode', 'service',
972
+ 'Record serialization failed: %s' % str(err))
973
+ return self.parent.response
974
+
975
+ hopcount = int(self.parent.kvp.get('hopcount', 2)) - 1
976
+
977
+ if ('federatedcatalogues' in self.parent.config and
978
+ self.parent.kvp.get('distributedsearch') and
979
+ hopcount > 0):
980
+
981
+ LOGGER.debug('DistributedSearch specified (hopCount: %s).', hopcount)
982
+
983
+ from owslib.csw import CatalogueServiceWeb
984
+ from owslib.ows import ExceptionReport
985
+ for fedcat in self.parent.config.get('federatedcatalogues', []):
986
+ LOGGER.info('Performing distributed search on federated \
987
+ catalogue: %s', fedcat)
988
+ try:
989
+ start_time = time()
990
+
991
+ remotecsw = CatalogueServiceWeb(fedcat, version='3.0.0', skip_caps=True)
992
+ if str(self.parent.request).startswith('http'):
993
+ self.parent.request = self.parent.request.split('?')[-1]
994
+ self.parent.request = self.parent.request.replace('mode=opensearch', '')
995
+ remotecsw.getrecords(xml=self.parent.request,
996
+ esn=self.parent.kvp['elementsetname'],
997
+ outputschema=self.parent.kvp['outputschema'])
998
+
999
+ fsr = etree.SubElement(searchresults, util.nspath_eval(
1000
+ 'csw30:FederatedSearchResult',
1001
+ self.parent.context.namespaces),
1002
+ catalogueURL=fedcat)
1003
+
1004
+ msg = 'Distributed search results from catalogue %s: %s.' % (fedcat, remotecsw.results)
1005
+ LOGGER.debug(msg)
1006
+ fsr.append(etree.Comment(msg))
1007
+
1008
+ search_result = etree.SubElement(fsr, util.nspath_eval(
1009
+ 'csw30:searchResult', self.parent.context.namespaces),
1010
+ recordSchema=self.parent.kvp['outputschema'],
1011
+ elementSetName=self.parent.kvp['elementsetname'],
1012
+ numberOfRecordsMatched=str(remotecsw.results['matches']),
1013
+ numberOfRecordsReturned=str(remotecsw.results['returned']),
1014
+ nextRecord=str(remotecsw.results['nextrecord']),
1015
+ elapsedTime=str(get_elapsed_time(start_time, time())),
1016
+ status=get_resultset_status(
1017
+ remotecsw.results['matches'],
1018
+ remotecsw.results['nextrecord']))
1019
+
1020
+ for result in remotecsw.records.values():
1021
+ search_result.append(etree.fromstring(result.xml, self.parent.context.parser))
1022
+
1023
+ except ExceptionReport as err:
1024
+ error_string = 'remote CSW %s returned exception: ' % fedcat
1025
+ searchresults.append(etree.Comment(
1026
+ ' %s\n\n%s ' % (error_string, err)))
1027
+ LOGGER.exception(error_string)
1028
+ except Exception as err:
1029
+ error_string = 'remote CSW %s returned error: ' % fedcat
1030
+ searchresults.append(etree.Comment(
1031
+ ' %s\n\n%s ' % (error_string, err)))
1032
+ LOGGER.exception(error_string)
1033
+
1034
+ searchresults.attrib['elapsedTime'] = str(get_elapsed_time(self.parent.process_time_start, time()))
1035
+
1036
+ if 'responsehandler' in self.parent.kvp: # process the handler
1037
+ self.parent._process_responsehandler(etree.tostring(node,
1038
+ pretty_print=self.parent.pretty_print))
1039
+ else:
1040
+ return node
1041
+
1042
+ def getrecordbyid(self, raw=False):
1043
+ ''' Handle GetRecordById request '''
1044
+
1045
+ if 'id' not in self.parent.kvp:
1046
+ return self.exceptionreport('MissingParameterValue', 'id',
1047
+ 'Missing id parameter')
1048
+ if len(self.parent.kvp['id']) < 1:
1049
+ return self.exceptionreport('InvalidParameterValue', 'id',
1050
+ 'Invalid id parameter')
1051
+ if 'outputschema' not in self.parent.kvp:
1052
+ self.parent.kvp['outputschema'] = self.parent.context.namespaces['csw30']
1053
+
1054
+ if 'HTTP_ACCEPT' in self.parent.environ:
1055
+ LOGGER.debug('Detected HTTP Accept header: %s', self.parent.environ['HTTP_ACCEPT'])
1056
+ formats_match = False
1057
+ if 'outputformat' in self.parent.kvp:
1058
+ LOGGER.debug(self.parent.kvp['outputformat'])
1059
+ for ofmt in self.parent.environ['HTTP_ACCEPT'].split(','):
1060
+ LOGGER.info('Comparing %s and %s', ofmt, self.parent.kvp['outputformat'])
1061
+ if ofmt.split('/')[0] in self.parent.kvp['outputformat']:
1062
+ LOGGER.debug('FOUND OUTPUT MATCH')
1063
+ formats_match = True
1064
+ if not formats_match:
1065
+ return self.exceptionreport('InvalidParameterValue',
1066
+ 'outputformat', 'HTTP Accept header (%s) and outputformat (%s) must agree' %
1067
+ (self.parent.environ['HTTP_ACCEPT'], self.parent.kvp['outputformat']))
1068
+ else:
1069
+ for ofmt in self.parent.environ['HTTP_ACCEPT'].split(','):
1070
+ if ofmt in self.parent.context.model['operations']['GetRecords']['parameters']['outputFormat']['values']:
1071
+ self.parent.kvp['outputformat'] = ofmt
1072
+ break
1073
+
1074
+ if ('outputformat' in self.parent.kvp and
1075
+ self.parent.kvp['outputformat'] not in
1076
+ self.parent.context.model['operations']['GetRecordById']['parameters']
1077
+ ['outputFormat']['values']):
1078
+ return self.exceptionreport('InvalidParameterValue',
1079
+ 'outputformat', 'Invalid outputformat parameter %s' %
1080
+ self.parent.kvp['outputformat'])
1081
+
1082
+ if ('outputschema' in self.parent.kvp and self.parent.kvp['outputschema'] not in
1083
+ self.parent.context.model['operations']['GetRecordById']['parameters']
1084
+ ['outputSchema']['values']):
1085
+ return self.exceptionreport('InvalidParameterValue',
1086
+ 'outputschema', 'Invalid outputschema parameter %s' %
1087
+ self.parent.kvp['outputschema'])
1088
+
1089
+ if 'outputformat' in self.parent.kvp:
1090
+ self.parent.contenttype = self.parent.kvp['outputformat']
1091
+ if self.parent.kvp['outputformat'] == 'application/atom+xml':
1092
+ self.parent.kvp['outputschema'] = self.parent.context.namespaces['atom']
1093
+ self.parent.mode = 'opensearch'
1094
+
1095
+ if 'elementsetname' not in self.parent.kvp:
1096
+ self.parent.kvp['elementsetname'] = 'summary'
1097
+ else:
1098
+ if (self.parent.kvp['elementsetname'] not in
1099
+ self.parent.context.model['operations']['GetRecordById']['parameters']
1100
+ ['ElementSetName']['values']):
1101
+ return self.exceptionreport('InvalidParameterValue',
1102
+ 'elementsetname', 'Invalid elementsetname parameter %s' %
1103
+ self.parent.kvp['elementsetname'])
1104
+
1105
+ # query repository
1106
+ LOGGER.info('Querying repository with ids: %s', self.parent.kvp['id'])
1107
+ results = self.parent.repository.query_ids([self.parent.kvp['id']])
1108
+
1109
+ if raw: # GetRepositoryItem request
1110
+ LOGGER.debug('GetRepositoryItem request.')
1111
+ if len(results) > 0:
1112
+ return etree.fromstring(util.getqattr(results[0],
1113
+ self.parent.context.md_core_model['mappings']['pycsw:XML']), self.parent.context.parser)
1114
+
1115
+ for result in results:
1116
+ node_ = None
1117
+ if self.parent.xslts:
1118
+ try:
1119
+ node_ = self.parent._render_xslt(result)
1120
+ except Exception as err:
1121
+ self.parent.response = self.exceptionreport(
1122
+ 'NoApplicableCode', 'service',
1123
+ 'XSLT transformation failed. Check server logs for errors %s' % str(err))
1124
+ return self.parent.response
1125
+ if node_ is not None:
1126
+ node = node_
1127
+ else:
1128
+ if (util.getqattr(result,
1129
+ self.parent.context.md_core_model['mappings']['pycsw:Typename']) == 'csw:Record'
1130
+ and self.parent.kvp['outputschema'] ==
1131
+ 'http://www.opengis.net/cat/csw/3.0'):
1132
+ # serialize record inline
1133
+ node = self._write_record(
1134
+ result, self.parent.repository.queryables['_all'])
1135
+ elif (self.parent.kvp['outputschema'] ==
1136
+ 'http://www.opengis.net/cat/csw/3.0'):
1137
+ # serialize into csw:Record model
1138
+ typename = None
1139
+
1140
+ for prof in self.parent.profiles['loaded']: # find source typename
1141
+ if self.parent.profiles['loaded'][prof].typename in \
1142
+ [util.getqattr(result, self.parent.context.md_core_model['mappings']['pycsw:Typename'])]:
1143
+ typename = self.parent.profiles['loaded'][prof].typename
1144
+ break
1145
+
1146
+ if typename is not None:
1147
+ util.transform_mappings(
1148
+ self.parent.repository.queryables['_all'],
1149
+ self.parent.context.model['typenames'][typename][
1150
+ 'mappings']['csw:Record']
1151
+ )
1152
+
1153
+ node = self._write_record( result, self.parent.repository.queryables['_all'])
1154
+ elif self.parent.kvp['outputschema'] in self.parent.outputschemas: # use outputschema serializer
1155
+ node = self.parent.outputschemas[self.parent.kvp['outputschema']].write_record(result, self.parent.kvp['elementsetname'], self.parent.context, self.parent.config['server'].get('url'))
1156
+ else: # it's a profile output
1157
+ node = self.parent.profiles['loaded'][self.parent.kvp['outputschema']].write_record(
1158
+ result, self.parent.kvp['elementsetname'],
1159
+ self.parent.kvp['outputschema'], self.parent.repository.queryables['_all'])
1160
+
1161
+ if raw and len(results) == 0:
1162
+ return None
1163
+
1164
+ if len(results) == 0:
1165
+ return self.exceptionreport('NotFound', 'id',
1166
+ 'No repository item found for \'%s\'' % self.parent.kvp['id'])
1167
+
1168
+ return node
1169
+
1170
+ def getrepositoryitem(self):
1171
+ ''' Handle GetRepositoryItem request '''
1172
+
1173
+ # similar to GetRecordById without csw:* wrapping
1174
+ node = self.parent.getrecordbyid(raw=True)
1175
+ if node is None:
1176
+ return self.exceptionreport('NotFound', 'id',
1177
+ 'No repository item found for \'%s\'' % self.parent.kvp['id'])
1178
+ else:
1179
+ return node
1180
+
1181
+ def transaction(self):
1182
+ ''' Handle Transaction request '''
1183
+
1184
+ try:
1185
+ self.parent._test_manager()
1186
+ except Exception as err:
1187
+ return self.exceptionreport('NoApplicableCode', 'transaction',
1188
+ str(err))
1189
+
1190
+ inserted = 0
1191
+ updated = 0
1192
+ deleted = 0
1193
+
1194
+ insertresults = []
1195
+
1196
+ LOGGER.debug('Transaction list: %s', self.parent.kvp['transactions'])
1197
+
1198
+ for ttype in self.parent.kvp['transactions']:
1199
+ if ttype['type'] == 'insert':
1200
+ try:
1201
+ record = metadata.parse_record(self.parent.context,
1202
+ ttype['xml'], self.parent.repository)[0]
1203
+ except Exception as err:
1204
+ LOGGER.exception('Transaction (insert) failed')
1205
+ return self.exceptionreport('NoApplicableCode', 'insert',
1206
+ 'Transaction (insert) failed: record parsing failed: %s' \
1207
+ % str(err))
1208
+
1209
+ LOGGER.debug('Transaction operation: %s', record)
1210
+
1211
+ if not hasattr(record,
1212
+ self.parent.context.md_core_model['mappings']['pycsw:Identifier']):
1213
+ return self.exceptionreport('NoApplicableCode',
1214
+ 'insert', 'Record requires an identifier')
1215
+
1216
+ # insert new record
1217
+ try:
1218
+ self.parent.repository.insert(record, 'local',
1219
+ util.get_today_and_now())
1220
+
1221
+ inserted += 1
1222
+ insertresults.append(
1223
+ {'identifier': getattr(record,
1224
+ self.parent.context.md_core_model['mappings']['pycsw:Identifier']),
1225
+ 'title': getattr(record,
1226
+ self.parent.context.md_core_model['mappings']['pycsw:Title'])})
1227
+ except Exception as err:
1228
+ LOGGER.exception('Transaction (insert) failed')
1229
+ return self.exceptionreport('NoApplicableCode',
1230
+ 'insert', 'Transaction (insert) failed: %s.' % str(err))
1231
+
1232
+ elif ttype['type'] == 'update':
1233
+ if 'constraint' not in ttype:
1234
+ # update full existing resource in repository
1235
+ try:
1236
+ record = metadata.parse_record(self.parent.context,
1237
+ ttype['xml'], self.parent.repository)[0]
1238
+ identifier = getattr(record,
1239
+ self.parent.context.md_core_model['mappings']['pycsw:Identifier'])
1240
+ except Exception as err:
1241
+ return self.exceptionreport('NoApplicableCode', 'insert',
1242
+ 'Transaction (update) failed: record parsing failed: %s' \
1243
+ % str(err))
1244
+
1245
+ # query repository to see if record already exists
1246
+ LOGGER.info('checking if record exists (%s)', identifier)
1247
+
1248
+ results = self.parent.repository.query_ids(ids=[identifier])
1249
+
1250
+ if len(results) == 0:
1251
+ LOGGER.debug('id %s does not exist in repository', identifier)
1252
+ else: # existing record, it's an update
1253
+ try:
1254
+ self.parent.repository.update(record)
1255
+ updated += 1
1256
+ except Exception as err:
1257
+ return self.exceptionreport('NoApplicableCode',
1258
+ 'update',
1259
+ 'Transaction (update) failed: %s.' % str(err))
1260
+ else: # update by record property and constraint
1261
+ # get / set XPath for property names
1262
+ for rp in ttype['recordproperty']:
1263
+ if rp['name'] not in self.parent.repository.queryables['_all']:
1264
+ # is it an XPath?
1265
+ if rp['name'].find('/') != -1:
1266
+ # scan outputschemas; if match, bind
1267
+ for osch in self.parent.outputschemas.values():
1268
+ for key, value in osch.XPATH_MAPPINGS.items():
1269
+ if value == rp['name']: # match
1270
+ rp['rp'] = {'xpath': value, 'name': key}
1271
+ rp['rp']['dbcol'] = self.parent.repository.queryables['_all'][key]
1272
+ break
1273
+ else:
1274
+ return self.exceptionreport('NoApplicableCode',
1275
+ 'update', 'Transaction (update) failed: invalid property2: %s.' % str(rp['name']))
1276
+ else:
1277
+ rp['rp']= \
1278
+ self.parent.repository.queryables['_all'][rp['name']]
1279
+
1280
+ LOGGER.debug('Record Properties: %s', ttype['recordproperty'])
1281
+ try:
1282
+ updated += self.parent.repository.update(record=None,
1283
+ recprops=ttype['recordproperty'],
1284
+ constraint=ttype['constraint'])
1285
+ except Exception as err:
1286
+ LOGGER.exception('Transaction (update) failed')
1287
+ return self.exceptionreport('NoApplicableCode',
1288
+ 'update',
1289
+ 'Transaction (update) failed: %s.' % str(err))
1290
+
1291
+ elif ttype['type'] == 'delete':
1292
+ deleted += self.parent.repository.delete(ttype['constraint'])
1293
+
1294
+ node = etree.Element(util.nspath_eval('csw30:TransactionResponse',
1295
+ self.parent.context.namespaces), nsmap=self.parent.context.namespaces, version='3.0.0')
1296
+
1297
+ node.attrib[util.nspath_eval('xsi:schemaLocation',
1298
+ self.parent.context.namespaces)] = '%s %s/csw/3.0/cswTransaction.xsd' % \
1299
+ (self.parent.context.namespaces['csw30'], self.parent.config['server'].get('ogc_schemas_base'))
1300
+
1301
+ node.append(
1302
+ self._write_transactionsummary(
1303
+ inserted=inserted, updated=updated, deleted=deleted))
1304
+
1305
+ if (len(insertresults) > 0 and self.parent.kvp['verboseresponse']):
1306
+ # show insert result identifiers
1307
+ node.append(self._write_verboseresponse(insertresults))
1308
+
1309
+ return node
1310
+
1311
+ def harvest(self):
1312
+ ''' Handle Harvest request '''
1313
+
1314
+ service_identifier = None
1315
+ old_identifier = None
1316
+ deleted = []
1317
+
1318
+ try:
1319
+ self.parent._test_manager()
1320
+ except Exception as err:
1321
+ return self.exceptionreport('NoApplicableCode', 'harvest', str(err))
1322
+
1323
+ if self.parent.requesttype == 'GET':
1324
+ if 'resourcetype' not in self.parent.kvp:
1325
+ return self.exceptionreport('MissingParameterValue',
1326
+ 'resourcetype', 'Missing resourcetype parameter')
1327
+ if 'source' not in self.parent.kvp:
1328
+ return self.exceptionreport('MissingParameterValue',
1329
+ 'source', 'Missing source parameter')
1330
+
1331
+ # validate resourcetype
1332
+ if (self.parent.kvp['resourcetype'] not in
1333
+ self.parent.context.model['operations']['Harvest']['parameters']['ResourceType']
1334
+ ['values']):
1335
+ return self.exceptionreport('InvalidParameterValue',
1336
+ 'resourcetype', 'Invalid resource type parameter: %s.\
1337
+ Allowable resourcetype values: %s' % (self.parent.kvp['resourcetype'],
1338
+ ','.join(sorted(self.parent.context.model['operations']['Harvest']['parameters']
1339
+ ['ResourceType']['values']))))
1340
+
1341
+ if (self.parent.kvp['resourcetype'].find('opengis.net') == -1 and
1342
+ self.parent.kvp['resourcetype'].find('urn:geoss:waf') == -1):
1343
+ # fetch content-based resource
1344
+ LOGGER.info('Fetching resource %s', self.parent.kvp['source'])
1345
+ try:
1346
+ content = util.http_request('GET', self.parent.kvp['source'])
1347
+ except Exception as err:
1348
+ errortext = 'Error fetching resource %s.\nError: %s.' % \
1349
+ (self.parent.kvp['source'], str(err))
1350
+ LOGGER.exception(errortext)
1351
+ return self.exceptionreport('InvalidParameterValue', 'source',
1352
+ errortext)
1353
+ else: # it's a service URL
1354
+ content = self.parent.kvp['source']
1355
+ # query repository to see if service already exists
1356
+ LOGGER.info('checking if service exists (%s)', content)
1357
+ results = self.parent.repository.query_source(content)
1358
+
1359
+ if len(results) > 0: # exists, keep identifier for update
1360
+ LOGGER.debug('Service already exists, keeping identifier and results')
1361
+ service_identifier = getattr(results[0], self.parent.context.md_core_model['mappings']['pycsw:Identifier'])
1362
+ service_results = results
1363
+ LOGGER.debug('Identifier is %s', service_identifier)
1364
+ # return self.exceptionreport('NoApplicableCode', 'source',
1365
+ # 'Insert failed: service %s already in repository' % content)
1366
+
1367
+
1368
+ if hasattr(self.parent.repository, 'local_ingest') and self.parent.repository.local_ingest:
1369
+ updated = 0
1370
+ deleted = []
1371
+ try:
1372
+ ir = self.parent.repository.insert(self.parent.kvp['resourcetype'], self.parent.kvp['source'])
1373
+ inserted = len(ir)
1374
+ except Exception as err:
1375
+ LOGGER.exception('Harvest (insert) failed')
1376
+ return self.exceptionreport('NoApplicableCode',
1377
+ 'source', 'Harvest (insert) failed: %s.' % str(err))
1378
+ else:
1379
+ # parse resource into record
1380
+ try:
1381
+ records_parsed = metadata.parse_record(self.parent.context,
1382
+ content, self.parent.repository, self.parent.kvp['resourcetype'],
1383
+ pagesize=self.parent.csw_harvest_pagesize)
1384
+ except Exception as err:
1385
+ LOGGER.exception(err)
1386
+ return self.exceptionreport('NoApplicableCode', 'source',
1387
+ 'Harvest failed: record parsing failed: %s' % str(err))
1388
+
1389
+ inserted = 0
1390
+ updated = 0
1391
+ ir = []
1392
+
1393
+ LOGGER.debug('Total Records parsed: %d', len(records_parsed))
1394
+ for record in records_parsed:
1395
+ if self.parent.kvp['resourcetype'] == 'urn:geoss:waf':
1396
+ src = record.source
1397
+ else:
1398
+ src = self.parent.kvp['source']
1399
+
1400
+ setattr(record, self.parent.context.md_core_model['mappings']['pycsw:Source'],
1401
+ src)
1402
+
1403
+ setattr(record, self.parent.context.md_core_model['mappings']['pycsw:InsertDate'],
1404
+ util.get_today_and_now())
1405
+
1406
+ identifier = getattr(record,
1407
+ self.parent.context.md_core_model['mappings']['pycsw:Identifier'])
1408
+ source = getattr(record,
1409
+ self.parent.context.md_core_model['mappings']['pycsw:Source'])
1410
+ insert_date = getattr(record,
1411
+ self.parent.context.md_core_model['mappings']['pycsw:InsertDate'])
1412
+ title = getattr(record,
1413
+ self.parent.context.md_core_model['mappings']['pycsw:Title'])
1414
+
1415
+ record_type = getattr(record, self.parent.context.md_core_model['mappings']['pycsw:Type'])
1416
+
1417
+ record_identifier = getattr(record, self.parent.context.md_core_model['mappings']['pycsw:Identifier'])
1418
+
1419
+ if record_type == 'service' and service_identifier is not None: # service endpoint
1420
+ LOGGER.info('Replacing service identifier from %s to %s', record_identifier, service_identifier)
1421
+ old_identifier = record_identifier
1422
+ identifier = record_identifier = service_identifier
1423
+ if (record_type != 'service' and service_identifier is not None
1424
+ and old_identifier is not None): # service resource
1425
+ if record_identifier.find(old_identifier) != -1:
1426
+ new_identifier = record_identifier.replace(old_identifier, service_identifier)
1427
+ LOGGER.info('Replacing service resource identifier from %s to %s', record_identifier, new_identifier)
1428
+ identifier = record_identifier = new_identifier
1429
+
1430
+ ir.append({'identifier': identifier, 'title': title})
1431
+
1432
+ results = []
1433
+ if 'source' not in self.parent.config['repository']:
1434
+ # query repository to see if record already exists
1435
+ LOGGER.info('checking if record exists (%s)', identifier)
1436
+ results = self.parent.repository.query_ids(ids=[identifier])
1437
+
1438
+ if len(results) == 0: # check for service identifier
1439
+ LOGGER.info('checking if service id exists (%s)', service_identifier)
1440
+ results = self.parent.repository.query_ids(ids=[service_identifier])
1441
+
1442
+ LOGGER.debug(str(results))
1443
+
1444
+ if len(results) == 0: # new record, it's a new insert
1445
+ inserted += 1
1446
+ try:
1447
+ tmp = self.parent.repository.insert(record, source, insert_date)
1448
+ if tmp is not None: ir = tmp
1449
+ except Exception as err:
1450
+ return self.exceptionreport('NoApplicableCode',
1451
+ 'source', 'Harvest (insert) failed: %s.' % str(err))
1452
+ else: # existing record, it's an update
1453
+ if source != results[0].source:
1454
+ # same identifier, but different source
1455
+ return self.exceptionreport('NoApplicableCode',
1456
+ 'source', 'Insert failed: identifier %s in repository\
1457
+ has source %s.' % (identifier, source))
1458
+
1459
+ try:
1460
+ self.parent.repository.update(record)
1461
+ except Exception as err:
1462
+ return self.exceptionreport('NoApplicableCode',
1463
+ 'source', 'Harvest (update) failed: %s.' % str(err))
1464
+ updated += 1
1465
+
1466
+ if service_identifier is not None:
1467
+ fresh_records = [str(i['identifier']) for i in ir]
1468
+ existing_records = [str(i.identifier) for i in service_results]
1469
+
1470
+ deleted = set(existing_records) - set(fresh_records)
1471
+ LOGGER.debug('Records to delete: %s', deleted)
1472
+
1473
+ for to_delete in deleted:
1474
+ delete_constraint = {
1475
+ 'type': 'filter',
1476
+ 'values': [to_delete],
1477
+ 'where': 'identifier = :pvalue0'
1478
+ }
1479
+ self.parent.repository.delete(delete_constraint)
1480
+
1481
+ node = etree.Element(util.nspath_eval('csw:HarvestResponse',
1482
+ self.parent.context.namespaces), nsmap=self.parent.context.namespaces)
1483
+
1484
+ node.attrib[util.nspath_eval('xsi:schemaLocation',
1485
+ self.parent.context.namespaces)] = \
1486
+ '%s %s/csw/3.0/cswHarvest.xsd' % (self.parent.context.namespaces['csw30'],
1487
+ self.parent.config['server'].get('ogc_schemas_base'))
1488
+
1489
+ node2 = etree.SubElement(node,
1490
+ util.nspath_eval('csw:TransactionResponse',
1491
+ self.parent.context.namespaces), version='2.0.2')
1492
+
1493
+ node2.append(
1494
+ self._write_transactionsummary(inserted=len(ir), updated=updated,
1495
+ deleted=len(deleted)))
1496
+
1497
+ if inserted > 0:
1498
+ # show insert result identifiers
1499
+ node2.append(self._write_verboseresponse(ir))
1500
+
1501
+ if 'responsehandler' in self.parent.kvp: # process the handler
1502
+ self.parent._process_responsehandler(etree.tostring(node,
1503
+ pretty_print=self.parent.pretty_print))
1504
+ else:
1505
+ return node
1506
+
1507
+ def _write_record(self, recobj, queryables):
1508
+ ''' Generate csw30:Record '''
1509
+ if self.parent.kvp['elementsetname'] == 'brief':
1510
+ elname = 'BriefRecord'
1511
+ elif self.parent.kvp['elementsetname'] == 'summary':
1512
+ elname = 'SummaryRecord'
1513
+ else:
1514
+ elname = 'Record'
1515
+
1516
+ record = etree.Element(util.nspath_eval('csw30:%s' % elname,
1517
+ self.parent.context.namespaces), nsmap=self.parent.context.namespaces)
1518
+
1519
+ if ('elementname' in self.parent.kvp and
1520
+ len(self.parent.kvp['elementname']) > 0):
1521
+ for req_term in ['dc:identifier', 'dc:title']:
1522
+ if req_term not in self.parent.kvp['elementname']:
1523
+ value = util.getqattr(recobj, queryables[req_term]['dbcol'])
1524
+ etree.SubElement(record,
1525
+ util.nspath_eval(req_term,
1526
+ self.parent.context.namespaces)).text = value
1527
+ for elemname in self.parent.kvp['elementname']:
1528
+ if (elemname.find('BoundingBox') != -1 or
1529
+ elemname.find('Envelope') != -1):
1530
+ bboxel = write_boundingbox(util.getqattr(recobj,
1531
+ self.parent.context.md_core_model['mappings']['pycsw:BoundingBox']),
1532
+ self.parent.context.namespaces)
1533
+ if bboxel is not None:
1534
+ record.append(bboxel)
1535
+ else:
1536
+ value = util.getqattr(recobj, queryables[elemname]['dbcol'])
1537
+ elem = etree.SubElement(record,
1538
+ util.nspath_eval(elemname,
1539
+ self.parent.context.namespaces))
1540
+ if value:
1541
+ elem.text = value
1542
+ elif 'elementsetname' in self.parent.kvp:
1543
+ if (self.parent.kvp['elementsetname'] == 'full' and
1544
+ util.getqattr(recobj, self.parent.context.md_core_model['mappings']\
1545
+ ['pycsw:Typename']) == 'csw:Record' and
1546
+ util.getqattr(recobj, self.parent.context.md_core_model['mappings']\
1547
+ ['pycsw:Schema']) == 'http://www.opengis.net/cat/csw/3.0' and
1548
+ util.getqattr(recobj, self.parent.context.md_core_model['mappings']\
1549
+ ['pycsw:Type']) != 'service'):
1550
+ # dump record as is and exit
1551
+ return etree.fromstring(util.getqattr(recobj,
1552
+ self.parent.context.md_core_model['mappings']['pycsw:XML']), self.parent.context.parser)
1553
+
1554
+ etree.SubElement(record,
1555
+ util.nspath_eval('dc:identifier', self.parent.context.namespaces)).text = \
1556
+ util.getqattr(recobj,
1557
+ self.parent.context.md_core_model['mappings']['pycsw:Identifier'])
1558
+
1559
+ for i in ['dc:title', 'dc:type']:
1560
+ val = util.getqattr(recobj, queryables[i]['dbcol'])
1561
+ if not val:
1562
+ val = ''
1563
+ etree.SubElement(record, util.nspath_eval(i,
1564
+ self.parent.context.namespaces)).text = val
1565
+
1566
+ if self.parent.kvp['elementsetname'] in ['summary', 'full']:
1567
+ # add summary elements
1568
+ keywords = util.getqattr(recobj, queryables['dc:subject']['dbcol'])
1569
+ if keywords is not None:
1570
+ for keyword in keywords.split(','):
1571
+ etree.SubElement(record,
1572
+ util.nspath_eval('dc:subject',
1573
+ self.parent.context.namespaces)).text = keyword
1574
+
1575
+ val = util.getqattr(recobj, self.parent.context.md_core_model['mappings']['pycsw:TopicCategory'])
1576
+ if val:
1577
+ etree.SubElement(record,
1578
+ util.nspath_eval('dc:subject',
1579
+ self.parent.context.namespaces), scheme='http://www.isotc211.org/2005/resources/Codelist/gmxCodelists.xml#MD_TopicCategoryCode').text = val
1580
+
1581
+ val = util.getqattr(recobj, queryables['dc:format']['dbcol'])
1582
+ if val:
1583
+ etree.SubElement(record,
1584
+ util.nspath_eval('dc:format',
1585
+ self.parent.context.namespaces)).text = val
1586
+
1587
+ # links
1588
+ rlinks = util.getqattr(recobj,
1589
+ self.parent.context.md_core_model['mappings']['pycsw:Links'])
1590
+
1591
+ if rlinks:
1592
+ LOGGER.info(f'link type: {type(rlinks)}')
1593
+ for link in util.jsonify_links(rlinks):
1594
+ ref = etree.SubElement(record, util.nspath_eval('dct:references',
1595
+ self.parent.context.namespaces))
1596
+ if link.get('protocol'):
1597
+ ref.attrib['scheme'] = link['protocol']
1598
+ ref.text = link['url']
1599
+
1600
+ for i in ['dc:relation', 'dct:modified', 'dct:abstract']:
1601
+ val = util.getqattr(recobj, queryables[i]['dbcol'])
1602
+ if val is not None:
1603
+ etree.SubElement(record,
1604
+ util.nspath_eval(i, self.parent.context.namespaces)).text = val
1605
+
1606
+ if self.parent.kvp['elementsetname'] == 'full': # add full elements
1607
+ for i in ['dc:date', 'dc:creator', \
1608
+ 'dc:publisher', 'dc:contributor', 'dc:source', \
1609
+ 'dc:language', 'dc:rights', 'dct:alternative']:
1610
+ val = util.getqattr(recobj, queryables[i]['dbcol'])
1611
+ if val:
1612
+ etree.SubElement(record,
1613
+ util.nspath_eval(i, self.parent.context.namespaces)).text = val
1614
+ val = util.getqattr(recobj, queryables['dct:spatial']['dbcol'])
1615
+ if val:
1616
+ etree.SubElement(record,
1617
+ util.nspath_eval('dct:spatial', self.parent.context.namespaces), scheme='http://www.opengis.net/def/crs').text = val
1618
+
1619
+ # always write out ows:BoundingBox
1620
+ bboxel = write_boundingbox(getattr(recobj,
1621
+ self.parent.context.md_core_model['mappings']['pycsw:BoundingBox']),
1622
+ self.parent.context.namespaces)
1623
+
1624
+ if bboxel is not None:
1625
+ record.append(bboxel)
1626
+
1627
+ if self.parent.kvp['elementsetname'] != 'brief': # add temporal extent
1628
+ begin = util.getqattr(record, self.parent.context.md_core_model['mappings']['pycsw:TempExtent_begin'])
1629
+ end = util.getqattr(record, self.parent.context.md_core_model['mappings']['pycsw:TempExtent_end'])
1630
+
1631
+ if begin or end:
1632
+ tempext = etree.SubElement(record, util.nspath_eval('csw30:TemporalExtent', self.parent.context.namespaces))
1633
+ if begin:
1634
+ etree.SubElement(record, util.nspath_eval('csw30:begin', self.parent.context.namespaces)).text = begin
1635
+ if end:
1636
+ etree.SubElement(record, util.nspath_eval('csw30:end', self.parent.context.namespaces)).text = end
1637
+
1638
+ return record
1639
+
1640
+ def _parse_constraint(self, element):
1641
+ ''' Parse csw:Constraint '''
1642
+
1643
+ query = {}
1644
+
1645
+ tmp = element.find(util.nspath_eval('fes20:Filter', self.parent.context.namespaces))
1646
+ if tmp is not None:
1647
+ LOGGER.debug('Filter constraint specified')
1648
+ try:
1649
+ query['type'] = 'filter'
1650
+ query['where'], query['values'] = fes2.parse(tmp,
1651
+ self.parent.repository.queryables['_all'], self.parent.repository.dbtype,
1652
+ self.parent.context.namespaces, self.parent.orm, self.parent.language['text'], self.parent.repository.fts)
1653
+ query['_dict'] = xml2dict(etree.tostring(tmp), self.parent.context.namespaces)
1654
+ except Exception as err:
1655
+ return 'Invalid Filter request: %s' % err
1656
+
1657
+ tmp = element.find(util.nspath_eval('csw30:CqlText', self.parent.context.namespaces))
1658
+ if tmp is not None:
1659
+ LOGGER.debug('CQL specified: %s.', tmp.text)
1660
+ try:
1661
+ LOGGER.info('Transforming CQL into OGC Filter')
1662
+ query['type'] = 'filter'
1663
+ cql = cql2fes(tmp.text, self.parent.context.namespaces, fes_version='2.0')
1664
+ query['where'], query['values'] = fes2.parse(cql,
1665
+ self.parent.repository.queryables['_all'], self.parent.repository.dbtype,
1666
+ self.parent.context.namespaces, self.parent.orm, self.parent.language['text'], self.parent.repository.fts)
1667
+ query['_dict'] = xml2dict(etree.tostring(cql), self.parent.context.namespaces)
1668
+ except Exception as err:
1669
+ LOGGER.exception('Invalid CQL request: %s', tmp.text)
1670
+ LOGGER.exception('Error message: %s', err)
1671
+ return 'Invalid CQL request'
1672
+ return query
1673
+
1674
+ def parse_postdata(self, postdata):
1675
+ ''' Parse POST XML '''
1676
+
1677
+ request = {}
1678
+ try:
1679
+ LOGGER.info('Parsing %s.', postdata)
1680
+ doc = etree.fromstring(postdata, self.parent.context.parser)
1681
+ except Exception as err:
1682
+ errortext = \
1683
+ 'Exception: document not well-formed.\nError: %s.' % str(err)
1684
+ LOGGER.exception(errortext)
1685
+ return errortext
1686
+
1687
+ # if this is a SOAP request, get to SOAP-ENV:Body/csw:*
1688
+ if (doc.tag == util.nspath_eval('soapenv:Envelope',
1689
+ self.parent.context.namespaces)):
1690
+
1691
+ LOGGER.debug('SOAP request specified')
1692
+ self.parent.soap = True
1693
+
1694
+ doc = doc.find(
1695
+ util.nspath_eval('soapenv:Body',
1696
+ self.parent.context.namespaces)).xpath('child::*')[0]
1697
+
1698
+ xsd_filename = '_wrapper.xsd'
1699
+ schema = os.path.join(self.parent.config['server'].get('home'),
1700
+ 'core', 'schemas', 'ogc', 'cat', 'csw', '3.0', xsd_filename)
1701
+
1702
+ try:
1703
+ # it is virtually impossible to validate a csw:Transaction
1704
+ # csw:Insert|csw:Update (with single child) XML document.
1705
+ # Only validate non csw:Transaction XML
1706
+
1707
+ if doc.find('.//%s' % util.nspath_eval('csw30:Insert',
1708
+ self.parent.context.namespaces)) is None and \
1709
+ len(doc.xpath('//csw30:Update/child::*',
1710
+ namespaces=self.parent.context.namespaces)) == 0:
1711
+
1712
+ LOGGER.info('Validating %s', postdata)
1713
+ schema = etree.XMLSchema(file=schema)
1714
+ parser = etree.XMLParser(schema=schema, resolve_entities=False)
1715
+ if hasattr(self.parent, 'soap') and self.parent.soap:
1716
+ # validate the body of the SOAP request
1717
+ doc = etree.fromstring(etree.tostring(doc), parser)
1718
+ else: # validate the request normally
1719
+ doc = etree.fromstring(postdata, parser)
1720
+ LOGGER.debug('Request is valid XML')
1721
+ else: # parse Transaction without validation
1722
+ doc = etree.fromstring(postdata, self.parent.context.parser)
1723
+ except Exception as err:
1724
+ errortext = \
1725
+ 'Exception: the document is not valid.\nError: %s' % str(err)
1726
+ LOGGER.exception(errortext)
1727
+ return errortext
1728
+
1729
+ request['request'] = etree.QName(doc).localname
1730
+ LOGGER.debug('Request operation %s specified.', request['request'])
1731
+ tmp = doc.find('.').attrib.get('service')
1732
+ if tmp is not None:
1733
+ request['service'] = tmp
1734
+
1735
+ tmp = doc.find('.').attrib.get('version')
1736
+ if tmp is not None:
1737
+ request['version'] = tmp
1738
+
1739
+ tmp = doc.find('.//%s' % util.nspath_eval('ows20:Version',
1740
+ self.parent.context.namespaces))
1741
+
1742
+ if tmp is not None:
1743
+ request['version'] = tmp.text
1744
+
1745
+ tmp = doc.find('.').attrib.get('updateSequence')
1746
+ if tmp is not None:
1747
+ request['updatesequence'] = tmp
1748
+
1749
+ # GetCapabilities
1750
+ if request['request'] == 'GetCapabilities':
1751
+ tmp = doc.find(util.nspath_eval('ows20:Sections',
1752
+ self.parent.context.namespaces))
1753
+ if tmp is not None:
1754
+ request['sections'] = ','.join([section.text for section in \
1755
+ doc.findall(util.nspath_eval('ows20:Sections/ows20:Section',
1756
+ self.parent.context.namespaces))])
1757
+
1758
+ tmp = doc.find(util.nspath_eval('ows20:AcceptFormats',
1759
+ self.parent.context.namespaces))
1760
+ if tmp is not None:
1761
+ request['acceptformats'] = ','.join([aformat.text for aformat in \
1762
+ doc.findall(util.nspath_eval('ows20:AcceptFormats/ows20:OutputFormat',
1763
+ self.parent.context.namespaces))])
1764
+
1765
+ tmp = doc.find(util.nspath_eval('ows20:AcceptVersions',
1766
+ self.parent.context.namespaces))
1767
+ if tmp is not None:
1768
+ request['acceptversions'] = ','.join([version.text for version in \
1769
+ doc.findall(util.nspath_eval('ows20:AcceptVersions/ows20:Version',
1770
+ self.parent.context.namespaces))])
1771
+
1772
+ # GetDomain
1773
+ if request['request'] == 'GetDomain':
1774
+ tmp = doc.find(util.nspath_eval('csw30:ParameterName',
1775
+ self.parent.context.namespaces))
1776
+ if tmp is not None:
1777
+ request['parametername'] = tmp.text
1778
+
1779
+ tmp = doc.find(util.nspath_eval('csw30:ValueReference',
1780
+ self.parent.context.namespaces))
1781
+ if tmp is not None:
1782
+ request['valuereference'] = tmp.text
1783
+
1784
+ # GetRecords
1785
+ if request['request'] == 'GetRecords':
1786
+ tmp = doc.find('.').attrib.get('outputSchema')
1787
+ request['outputschema'] = tmp if tmp is not None \
1788
+ else self.parent.context.namespaces['csw30']
1789
+
1790
+ tmp = doc.find('.').attrib.get('outputFormat')
1791
+ request['outputformat'] = tmp if tmp is not None \
1792
+ else 'application/xml'
1793
+
1794
+ tmp = doc.find('.').attrib.get('startPosition')
1795
+ request['startposition'] = tmp if tmp is not None else 1
1796
+
1797
+ tmp = doc.find('.').attrib.get('requestId')
1798
+ request['requestid'] = tmp if tmp is not None else None
1799
+
1800
+ tmp = doc.find('.').attrib.get('maxRecords')
1801
+ if tmp is not None:
1802
+ request['maxrecords'] = tmp
1803
+
1804
+ tmp = doc.find(util.nspath_eval('csw30:DistributedSearch',
1805
+ self.parent.context.namespaces))
1806
+ if tmp is not None:
1807
+ request['distributedsearch'] = True
1808
+ hopcount = tmp.attrib.get('hopCount')
1809
+ request['hopcount'] = int(hopcount) if hopcount is not None \
1810
+ else 2
1811
+ else:
1812
+ request['distributedsearch'] = False
1813
+
1814
+ tmp = doc.find(util.nspath_eval('csw30:ResponseHandler',
1815
+ self.parent.context.namespaces))
1816
+ if tmp is not None:
1817
+ request['responsehandler'] = tmp.text
1818
+
1819
+ tmp = doc.find(util.nspath_eval('csw30:Query/csw30:ElementSetName',
1820
+ self.parent.context.namespaces))
1821
+ request['elementsetname'] = tmp.text if tmp is not None else None
1822
+
1823
+ tmp = doc.find(util.nspath_eval(
1824
+ 'csw30:Query', self.parent.context.namespaces)).attrib.get('typeNames')
1825
+ request['typenames'] = tmp.split() if tmp is not None \
1826
+ else 'csw:Record'
1827
+
1828
+ request['elementname'] = [elname.text for elname in \
1829
+ doc.findall(util.nspath_eval('csw30:Query/csw30:ElementName',
1830
+ self.parent.context.namespaces))]
1831
+
1832
+ request['constraint'] = {}
1833
+ tmp = doc.find(util.nspath_eval('csw30:Query/csw30:Constraint',
1834
+ self.parent.context.namespaces))
1835
+
1836
+ if tmp is not None:
1837
+ request['constraint'] = self._parse_constraint(tmp)
1838
+ if isinstance(request['constraint'], str): # parse error
1839
+ return 'Invalid Constraint: %s' % request['constraint']
1840
+ else:
1841
+ LOGGER.debug('No csw30:Constraint (fes20:Filter or csw30:CqlText) \
1842
+ specified')
1843
+
1844
+ tmp = doc.find(util.nspath_eval('csw30:Query/fes20:SortBy',
1845
+ self.parent.context.namespaces))
1846
+ if tmp is not None:
1847
+ LOGGER.debug('Sorted query specified')
1848
+ request['sortby'] = {}
1849
+
1850
+ try:
1851
+ elname = tmp.find(util.nspath_eval(
1852
+ 'fes20:SortProperty/fes20:ValueReference',
1853
+ self.parent.context.namespaces)).text
1854
+
1855
+ request['sortby']['propertyname'] = \
1856
+ self.parent.repository.queryables['_all'][elname]['dbcol']
1857
+
1858
+ if (elname.find('BoundingBox') != -1 or
1859
+ elname.find('Envelope') != -1):
1860
+ # it's a spatial sort
1861
+ request['sortby']['spatial'] = True
1862
+ except Exception as err:
1863
+ errortext = \
1864
+ 'Invalid fes20:SortProperty/fes20:ValueReference: %s' % str(err)
1865
+ LOGGER.exception(errortext)
1866
+ return errortext
1867
+
1868
+ tmp2 = tmp.find(util.nspath_eval(
1869
+ 'fes20:SortProperty/fes20:SortOrder', self.parent.context.namespaces))
1870
+ request['sortby']['order'] = tmp2.text if tmp2 is not None \
1871
+ else 'ASC'
1872
+ else:
1873
+ request['sortby'] = None
1874
+
1875
+ # GetRecordById
1876
+ if request['request'] == 'GetRecordById':
1877
+ request['id'] = None
1878
+ tmp = doc.find(util.nspath_eval('csw30:Id', self.parent.context.namespaces))
1879
+ if tmp is not None:
1880
+ request['id'] = tmp.text
1881
+
1882
+ tmp = doc.find(util.nspath_eval('csw30:ElementSetName',
1883
+ self.parent.context.namespaces))
1884
+ request['elementsetname'] = tmp.text if tmp is not None \
1885
+ else 'summary'
1886
+
1887
+ tmp = doc.find('.').attrib.get('outputSchema')
1888
+ request['outputschema'] = tmp if tmp is not None \
1889
+ else self.parent.context.namespaces['csw30']
1890
+
1891
+ tmp = doc.find('.').attrib.get('outputFormat')
1892
+ if tmp is not None:
1893
+ request['outputformat'] = tmp
1894
+
1895
+ # Transaction
1896
+ if request['request'] == 'Transaction':
1897
+ request['verboseresponse'] = True
1898
+ tmp = doc.find('.').attrib.get('verboseResponse')
1899
+ if tmp is not None:
1900
+ if tmp in ['false', '0']:
1901
+ request['verboseresponse'] = False
1902
+
1903
+ tmp = doc.find('.').attrib.get('requestId')
1904
+ request['requestid'] = tmp if tmp is not None else None
1905
+
1906
+ request['transactions'] = []
1907
+
1908
+ for ttype in \
1909
+ doc.xpath('//csw30:Insert', namespaces=self.parent.context.namespaces):
1910
+ tname = ttype.attrib.get('typeName')
1911
+
1912
+ for mdrec in ttype.xpath('child::*'):
1913
+ xml = mdrec
1914
+ request['transactions'].append(
1915
+ {'type': 'insert', 'typename': tname, 'xml': xml})
1916
+
1917
+ for ttype in \
1918
+ doc.xpath('//csw30:Update', namespaces=self.parent.context.namespaces):
1919
+ child = ttype.xpath('child::*')
1920
+ update = {'type': 'update'}
1921
+
1922
+ if len(child) == 1: # it's a wholesale update
1923
+ update['xml'] = child[0]
1924
+ else: # it's a RecordProperty with Constraint Update
1925
+ update['recordproperty'] = []
1926
+
1927
+ for recprop in ttype.findall(
1928
+ util.nspath_eval('csw:RecordProperty',
1929
+ self.parent.context.namespaces)):
1930
+ rpname = recprop.find(util.nspath_eval('csw30:Name',
1931
+ self.parent.context.namespaces)).text
1932
+ rpvalue = recprop.find(
1933
+ util.nspath_eval('csw30:Value',
1934
+ self.parent.context.namespaces)).text
1935
+
1936
+ update['recordproperty'].append(
1937
+ {'name': rpname, 'value': rpvalue})
1938
+
1939
+ update['constraint'] = self._parse_constraint(
1940
+ ttype.find(util.nspath_eval('csw30:Constraint',
1941
+ self.parent.context.namespaces)))
1942
+
1943
+ request['transactions'].append(update)
1944
+
1945
+ for ttype in \
1946
+ doc.xpath('//csw30:Delete', namespaces=self.parent.context.namespaces):
1947
+ tname = ttype.attrib.get('typeName')
1948
+ constraint = self._parse_constraint(
1949
+ ttype.find(util.nspath_eval('csw30:Constraint',
1950
+ self.parent.context.namespaces)))
1951
+
1952
+ if isinstance(constraint, str): # parse error
1953
+ return 'Invalid Constraint: %s' % constraint
1954
+
1955
+ request['transactions'].append(
1956
+ {'type': 'delete', 'typename': tname, 'constraint': constraint})
1957
+
1958
+ # Harvest
1959
+ if request['request'] == 'Harvest':
1960
+ request['source'] = doc.find(util.nspath_eval('csw30:Source',
1961
+ self.parent.context.namespaces)).text
1962
+
1963
+ request['resourcetype'] = \
1964
+ doc.find(util.nspath_eval('csw30:ResourceType',
1965
+ self.parent.context.namespaces)).text
1966
+
1967
+ tmp = doc.find(util.nspath_eval('csw30:ResourceFormat',
1968
+ self.parent.context.namespaces))
1969
+ if tmp is not None:
1970
+ request['resourceformat'] = tmp.text
1971
+ else:
1972
+ request['resourceformat'] = 'application/xml'
1973
+
1974
+ tmp = doc.find(util.nspath_eval('csw30:HarvestInterval',
1975
+ self.parent.context.namespaces))
1976
+ if tmp is not None:
1977
+ request['harvestinterval'] = tmp.text
1978
+
1979
+ tmp = doc.find(util.nspath_eval('csw30:ResponseHandler',
1980
+ self.parent.context.namespaces))
1981
+ if tmp is not None:
1982
+ request['responsehandler'] = tmp.text
1983
+ return request
1984
+
1985
+ def _write_transactionsummary(self, inserted=0, updated=0, deleted=0):
1986
+ ''' Write csw:TransactionSummary construct '''
1987
+ node = etree.Element(util.nspath_eval('csw30:TransactionSummary',
1988
+ self.parent.context.namespaces))
1989
+
1990
+ if 'requestid' in self.parent.kvp and self.parent.kvp['requestid'] is not None:
1991
+ node.attrib['requestId'] = self.parent.kvp['requestid']
1992
+
1993
+ etree.SubElement(node, util.nspath_eval('csw30:totalInserted',
1994
+ self.parent.context.namespaces)).text = str(inserted)
1995
+
1996
+ etree.SubElement(node, util.nspath_eval('csw30:totalUpdated',
1997
+ self.parent.context.namespaces)).text = str(updated)
1998
+
1999
+ etree.SubElement(node, util.nspath_eval('csw30:totalDeleted',
2000
+ self.parent.context.namespaces)).text = str(deleted)
2001
+
2002
+ return node
2003
+
2004
+ def _write_acknowledgement(self, root=True):
2005
+ ''' Generate csw:Acknowledgement '''
2006
+ node = etree.Element(util.nspath_eval('csw30:Acknowledgement',
2007
+ self.parent.context.namespaces),
2008
+ nsmap = self.parent.context.namespaces, timeStamp=util.get_today_and_now())
2009
+
2010
+ if root:
2011
+ node.attrib[util.nspath_eval('xsi:schemaLocation',
2012
+ self.parent.context.namespaces)] = \
2013
+ '%s %s/cat/csw/3.0/cswAll.xsd' % (self.parent.context.namespaces['csw30'], \
2014
+ self.parent.config['server'].get('ogc_schemas_base'))
2015
+
2016
+ node1 = etree.SubElement(node, util.nspath_eval('csw30:EchoedRequest',
2017
+ self.parent.context.namespaces))
2018
+ if self.parent.requesttype == 'POST':
2019
+ node1.append(etree.fromstring(self.parent.request, self.parent.context.parser))
2020
+ else: # GET
2021
+ node2 = etree.SubElement(node1, util.nspath_eval('ows:Get',
2022
+ self.parent.context.namespaces))
2023
+
2024
+ node2.text = self.parent.request
2025
+
2026
+ if self.parent.asynchronous:
2027
+ etree.SubElement(node, util.nspath_eval('csw30:RequestId',
2028
+ self.parent.context.namespaces)).text = self.parent.kvp['requestid']
2029
+
2030
+ return node
2031
+
2032
+ def _write_verboseresponse(self, insertresults):
2033
+ ''' show insert result identifiers '''
2034
+ insertresult = etree.Element(util.nspath_eval('csw30:InsertResult',
2035
+ self.parent.context.namespaces))
2036
+ for ir in insertresults:
2037
+ briefrec = etree.SubElement(insertresult,
2038
+ util.nspath_eval('csw30:BriefRecord',
2039
+ self.parent.context.namespaces))
2040
+
2041
+ etree.SubElement(briefrec,
2042
+ util.nspath_eval('dc:identifier',
2043
+ self.parent.context.namespaces)).text = ir['identifier']
2044
+
2045
+ etree.SubElement(briefrec,
2046
+ util.nspath_eval('dc:title',
2047
+ self.parent.context.namespaces)).text = ir['title']
2048
+
2049
+ return insertresult
2050
+
2051
+ def _write_allowed_values(self, values):
2052
+ ''' design pattern to write ows20:AllowedValues '''
2053
+
2054
+ allowed_values = etree.Element(util.nspath_eval('ows20:AllowedValues',
2055
+ self.parent.context.namespaces))
2056
+
2057
+ for value in sorted(values):
2058
+ etree.SubElement(allowed_values,
2059
+ util.nspath_eval('ows20:Value',
2060
+ self.parent.context.namespaces)).text = str(value)
2061
+ return allowed_values
2062
+
2063
+ def exceptionreport(self, code, locator, text):
2064
+ ''' Generate ExceptionReport '''
2065
+ self.parent.exception = True
2066
+ self.parent.status = code
2067
+
2068
+ try:
2069
+ language = self.parent.config['server'].get('language')
2070
+ ogc_schemas_base = self.parent.config['server'].get('ogc_schemas_base')
2071
+ except Exception:
2072
+ LOGGER.debug('Dropping to default language and OGC schemas base')
2073
+ language = 'en-US'
2074
+ ogc_schemas_base = self.parent.context.ogc_schemas_base
2075
+
2076
+ node = etree.Element(util.nspath_eval('ows20:ExceptionReport',
2077
+ self.parent.context.namespaces), nsmap=self.parent.context.namespaces,
2078
+ version='3.0.0')
2079
+
2080
+ node.attrib['{http://www.w3.org/XML/1998/namespace}lang'] = language
2081
+
2082
+ node.attrib[util.nspath_eval('xsi:schemaLocation',
2083
+ self.parent.context.namespaces)] = \
2084
+ '%s %s/ows/2.0/owsExceptionReport.xsd' % \
2085
+ (self.parent.context.namespaces['ows20'], ogc_schemas_base)
2086
+
2087
+ exception = etree.SubElement(node, util.nspath_eval('ows20:Exception',
2088
+ self.parent.context.namespaces),
2089
+ exceptionCode=code, locator=locator)
2090
+
2091
+ exception_text = etree.SubElement(exception,
2092
+ util.nspath_eval('ows20:ExceptionText',
2093
+ self.parent.context.namespaces))
2094
+
2095
+ try:
2096
+ exception_text.text = text
2097
+ except ValueError as err:
2098
+ exception_text.text = repr(text)
2099
+
2100
+ return node
2101
+
2102
+ def resolve_nsmap(self, list_):
2103
+ '''' Resolve typename bindings based on default and KVP namespaces '''
2104
+
2105
+ nsmap = {}
2106
+
2107
+ tns = []
2108
+
2109
+ LOGGER.debug('Namespace list pairs: %s', list_)
2110
+
2111
+ # bind KVP namespaces into typenames
2112
+ for ns in self.parent.kvp['namespace'].split(','):
2113
+ nspair = ns.split('(')[1].split(')')[0].split('=')
2114
+ if len(nspair) == 1: # default namespace
2115
+ nsmap['csw'] = nspair[1]
2116
+ else:
2117
+ nsmap[nspair[0]] = nspair[1]
2118
+
2119
+ LOGGER.debug('Namespace pairs: %s', nsmap)
2120
+
2121
+ for tn in list_:
2122
+ LOGGER.debug(tn)
2123
+ if tn.find(':') != -1: # resolve prefix
2124
+ prefix = tn.split(':')[0]
2125
+ if prefix in nsmap.keys(): # get uri
2126
+ uri = nsmap[prefix]
2127
+ newprefix = next(k for k, v in self.parent.context.namespaces.items() if v == uri)
2128
+ LOGGER.debug(uri)
2129
+ LOGGER.debug(prefix)
2130
+ LOGGER.debug(newprefix)
2131
+ #if prefix == 'csw30': newprefix = 'csw'
2132
+ newvalue = tn.replace(prefix, newprefix).replace('csw30', 'csw')
2133
+ else:
2134
+ newvalue = tn
2135
+ else: # default namespace
2136
+ newvalue = tn
2137
+
2138
+ tns.append(newvalue)
2139
+
2140
+
2141
+ LOGGER.debug(tns)
2142
+ return tns
2143
+
2144
+ def write_boundingbox(bbox, nsmap):
2145
+ ''' Generate ows20:BoundingBox '''
2146
+
2147
+ if bbox is not None:
2148
+ try:
2149
+ bbox2 = util.wkt2geom(bbox)
2150
+ except Exception as err:
2151
+ LOGGER.debug(f'Geometry parsing error: {err}')
2152
+ return None
2153
+
2154
+ if len(bbox2) == 4:
2155
+ boundingbox = etree.Element(util.nspath_eval('ows20:BoundingBox',
2156
+ nsmap), crs='http://www.opengis.net/def/crs/EPSG/0/4326',
2157
+ dimensions='2')
2158
+
2159
+ etree.SubElement(boundingbox, util.nspath_eval('ows20:LowerCorner',
2160
+ nsmap)).text = '%s %s' % (bbox2[1], bbox2[0])
2161
+
2162
+ etree.SubElement(boundingbox, util.nspath_eval('ows20:UpperCorner',
2163
+ nsmap)).text = '%s %s' % (bbox2[3], bbox2[2])
2164
+
2165
+ return boundingbox
2166
+ else:
2167
+ return None
2168
+ else:
2169
+ return None
2170
+ if nextrecord == 0:
2171
+ searchresult_status = 'complete'
2172
+ elif nextrecord > 0:
2173
+ searchresult_status = 'subset'
2174
+ elif matched == 0:
2175
+ searchresult_status = 'none'
2176
+
2177
+ def get_resultset_status(matched, nextrecord):
2178
+ ''' Helper function to assess status of a result set '''
2179
+
2180
+ status = 'subset' # default
2181
+
2182
+ if nextrecord == 0:
2183
+ status = 'complete'
2184
+ elif matched == 0:
2185
+ status = 'none'
2186
+
2187
+ return status
2188
+
2189
+
2190
+ def get_elapsed_time(begin, end):
2191
+ """Helper function to calculate elapsed time in milliseconds."""
2192
+
2193
+ return int((end - begin) * 1000)