Coverage for gws-app/gws/base/ows/server/templatelib.py: 82%

132 statements  

« prev     ^ index     » next       coverage.py v7.16.2, created at 2026-10-05 13:35 +0200

1"""Helper functions for OWS service templates.""" 

2 

3from typing import Optional, cast 

4 

5import gws 

6import gws.base.metadata 

7import gws.lib.gml 

8import gws.lib.uom 

9import gws.lib.datetimex as dtx 

10import gws.lib.xmlx as xmlx 

11import gws.base.ows.server as server 

12 

13from . import core, layer_caps, service 

14from gws.lib.xmlx import tag 

15 

16# OGC 06-121r9 Table 34 

17# Ordered sequence of two double values in decimal degrees, with longitude before latitude 

18def ows_wgs84_bounding_box(lc: core.LayerCaps, ns: str = 'OWS_11'): 

19 """Create a ``WGS84BoundingBox`` element for a layer. 

20 

21 Args: 

22 lc: Layer caps. 

23 ns: Namespace prefix of the OWS elements. 

24 

25 Returns: 

26 The XML element. 

27 """ 

28 

29 return tag( 

30 f'{ns}:WGS84BoundingBox', 

31 tag(f'{ns}:LowerCorner', coord_dms(lc.layer.wgsExtent[0]), ' ', coord_dms(lc.layer.wgsExtent[1])), 

32 tag(f'{ns}:UpperCorner', coord_dms(lc.layer.wgsExtent[2]), ' ', coord_dms(lc.layer.wgsExtent[3])), 

33 ) 

34 

35 

36# OGC 06-121r3 sec 7.4.4 

37def ows_service_identification(ta: server.TemplateArgs, ns: str = 'OWS_11'): 

38 """Create a ``ServiceIdentification`` element from the service metadata. 

39 

40 Args: 

41 ta: Template arguments. 

42 ns: Namespace prefix of the OWS elements. 

43 

44 Returns: 

45 The XML element. 

46 """ 

47 

48 md = ta.service.metadata 

49 

50 return tag( 

51 f'{ns}:ServiceIdentification', 

52 tag(f'{ns}:Title', md.title), 

53 tag(f'{ns}:Abstract', md.abstract), 

54 ows_keywords(md, ns), 

55 tag(f'{ns}:ServiceType', ta.service.protocol), 

56 tag(f'{ns}:ServiceTypeVersion', ta.version), 

57 tag(f'{ns}:Fees', md.fees) if md.fees else None, 

58 tag(f'{ns}:AccessConstraints', md.accessConstraints) if md.accessConstraints else None, 

59 ) 

60 

61 

62# OGC 06-121r3 sec 7.4.5 

63def ows_service_provider(ta: server.TemplateArgs, ns: str = 'OWS_11'): 

64 """Create a ``ServiceProvider`` element from the service contact metadata. 

65 

66 Args: 

67 ta: Template arguments. 

68 ns: Namespace prefix of the OWS elements. 

69 

70 Returns: 

71 The XML element. 

72 """ 

73 

74 md = ta.service.metadata 

75 

76 return tag( 

77 f'{ns}:ServiceProvider', 

78 tag(f'{ns}:ProviderName', md.contactProviderName), 

79 tag(f'{ns}:ProviderSite', {'XLINK:href': md.contactProviderSite}), 

80 tag( 

81 f'{ns}:ServiceContact', 

82 tag(f'{ns}:IndividualName', md.contactPerson), 

83 tag(f'{ns}:PositionName', md.contactPosition), 

84 tag( 

85 f'{ns}:ContactInfo', 

86 tag( 

87 f'{ns}:Phone', 

88 tag(f'{ns}:Voice', md.contactPhone), 

89 tag(f'{ns}:Facsimile', md.contactFax), 

90 ), 

91 tag( 

92 f'{ns}:Address', 

93 tag(f'{ns}:DeliveryPoint', md.contactAddress), 

94 tag(f'{ns}:City', md.contactCity), 

95 tag(f'{ns}:AdministrativeArea', md.contactArea), 

96 tag(f'{ns}:PostalCode', md.contactZip), 

97 tag(f'{ns}:Country', md.contactCountry), 

98 tag(f'{ns}:ElectronicMailAddress', md.contactEmail), 

99 ), 

100 tag(f'{ns}:OnlineResource', {'XLINK:href': md.contactUrl}), 

101 ), 

102 tag(f'{ns}:Role', md.contactRole), 

103 ), 

104 ) 

105 

106 

107# OGC 06-121r3 table 15,16,17 

108# OGC 06-121r3 11.2: 

109# A URL prefix is defined as a string including... mandatory question mark 

110 

111 

112def ows_service_url(ta: server.TemplateArgs, get=True, post=False, ns: str = 'OWS_11'): 

113 """Create ``DCP/HTTP/Get`` and ``DCP/HTTP/Post`` elements with the service URL. 

114 

115 Args: 

116 ta: Template arguments. 

117 get: Include the GET URL. 

118 post: Include the POST URL. 

119 ns: Namespace prefix of the OWS elements. 

120 

121 Yields: 

122 XML elements. 

123 """ 

124 

125 if get: 

126 yield tag( 

127 f'{ns}:DCP/{ns}:HTTP/{ns}:Get', 

128 {'XLINK:type': 'simple', 'XLINK:href': ta.serviceUrl + '?'}, 

129 ) 

130 if post: 

131 yield tag( 

132 f'{ns}:DCP/{ns}:HTTP/{ns}:Post', 

133 {'XLINK:type': 'simple', 'XLINK:href': ta.serviceUrl}, 

134 ) 

135 

136 

137def ows_value(value, ns: str = 'OWS_11'): 

138 """Create a ``Value`` element. 

139 

140 Args: 

141 value: Element text. 

142 ns: Namespace prefix of the OWS elements. 

143 

144 Returns: 

145 The XML element. 

146 """ 

147 

148 return tag(f'{ns}:Value', value) 

149 

150 

151def online_resource(url): 

152 """Create an ``OnlineResource`` element with a simple xlink. 

153 

154 Args: 

155 url: Link URL. 

156 

157 Returns: 

158 The XML element. 

159 """ 

160 

161 return tag('OnlineResource', {'XLINK:type': 'simple', 'XLINK:href': url}) 

162 

163 

164# OGC 01-068r3, 6.2.2 

165# The URL prefix shall end in either a '?' (in the absence of additional server-specific parameters) or a '&'. 

166# OGC 06-042, 6.3.3 

167# A URL prefix is defined... as a string including... mandatory question mark 

168 

169 

170def dcp_service_url(ta: server.TemplateArgs): 

171 """Create a ``DCPType/HTTP/Get`` element with the service URL (WMS style). 

172 

173 Args: 

174 ta: Template arguments. 

175 

176 Returns: 

177 The XML element. 

178 """ 

179 

180 return tag('DCPType/HTTP/Get', online_resource(ta.serviceUrl + '?')) 

181 

182 

183def legend_url_nested(ta: server.TemplateArgs, lc: core.LayerCaps, size=None): 

184 """Create a ``LegendURL`` element with nested ``Format`` and ``OnlineResource`` (WMS style). 

185 

186 Args: 

187 ta: Template arguments. 

188 lc: Layer caps. 

189 size: Legend size as ``(width, height)``, optional. 

190 

191 Returns: 

192 The XML element. 

193 """ 

194 

195 return tag( 

196 'LegendURL', 

197 {'width': size[0], 'height': size[1]} if size else {}, 

198 tag('Format', 'image/png'), 

199 online_resource(f'{ta.serviceUrl}?request=GetLegendGraphic&layer={lc.layerName}'), 

200 ) 

201 

202 

203def legend_url(ta: server.TemplateArgs, lc: core.LayerCaps, size=None): 

204 """Create a ``LegendURL`` element with the format and URL as attributes (WMTS style). 

205 

206 Args: 

207 ta: Template arguments. 

208 lc: Layer caps. 

209 size: Not used. 

210 

211 Returns: 

212 The XML element. 

213 """ 

214 

215 return tag( 

216 'LegendURL', 

217 { 

218 'format': 'image/png', 

219 'XLINK:href': f'{ta.serviceUrl}?request=GetLegendGraphic&layer={lc.layerName}', 

220 }, 

221 ) 

222 

223 

224def feature_name(ta: server.TemplateArgs, lc: core.LayerCaps) -> str: 

225 """Return the qualified feature type name of a layer, with the requested custom prefixes applied. 

226 

227 Args: 

228 ta: Template arguments. 

229 lc: Layer caps. 

230 

231 Returns: 

232 The name as ``prefix:name``, or the plain name if the layer has no namespace. 

233 """ 

234 

235 return layer_caps.qualified_feature_name(lc, ta.serviceRequest.customNamespacePrefixes) 

236 

237 

238def ows_keywords(md: gws.Metadata, ns: str = 'OWS_11'): 

239 """Create ``Keywords`` elements for the keyword groups in the metadata. 

240 

241 Args: 

242 md: Metadata. 

243 ns: Namespace prefix of the OWS elements. 

244 

245 Returns: 

246 A list of XML elements. 

247 """ 

248 

249 return [_ows_keyword_group(kg, ns) for kg in gws.base.metadata.keyword_groups(md)] 

250 

251 

252def _ows_keyword_group(kg: gws.base.metadata.KeywordGroup, ns: str): 

253 """Create a ``Keywords`` element for a keyword group.""" 

254 

255 tags = [] 

256 for kw in kg.keywords: 

257 tags.append(tag(f'{ns}:Keyword', kw)) 

258 if kg.codeSpace: 

259 tags.append(tag(f'{ns}:Type', {'codeSpace': kg.codeSpace}, kg.typeName)) 

260 return tag(f'{ns}:Keywords', tags) 

261 

262 

263def wms_keywords(md: gws.Metadata, with_vocabulary: bool = False): 

264 """Create a WMS ``KeywordList`` element. 

265 

266 Args: 

267 md: Metadata. 

268 with_vocabulary: Add the ``vocabulary`` attribute for keywords with a code space. 

269 

270 Returns: 

271 The XML element. 

272 """ 

273 

274 tags = [] 

275 for kg in gws.base.metadata.keyword_groups(md): 

276 for kw in kg.keywords: 

277 if kg.codeSpace and with_vocabulary: 

278 tags.append(tag('Keyword', kw, {'vocabulary': kg.codeSpace})) 

279 else: 

280 tags.append(tag('Keyword', kw)) 

281 return tag('KeywordList', tags) 

282 

283 

284def lon_lat_envelope(lc: core.LayerCaps): 

285 """Create a ``lonLatEnvelope`` element for a layer. 

286 

287 Args: 

288 lc: Layer caps. 

289 

290 Returns: 

291 The XML element. 

292 """ 

293 

294 return tag( 

295 'lonLatEnvelope', 

296 {'srsName': 'urn:ogc:def:crs:OGC:1.3:CRS84'}, 

297 tag('GML:pos', coord_dms(lc.layer.wgsExtent[0]), ' ', coord_dms(lc.layer.wgsExtent[1])), 

298 tag('GML:pos', coord_dms(lc.layer.wgsExtent[2]), ' ', coord_dms(lc.layer.wgsExtent[3])), 

299 ) 

300 

301 

302# Nested format (WFS 1, WMS): 

303# OGC 06-042 7.2.4.6.11 

304# The "type" attribute indicates the standard... The enclosed <Format> element... etc 

305 

306 

307def meta_links_nested(ta: server.TemplateArgs, md: gws.Metadata): 

308 """Create ``MetadataURL`` elements in the nested format for the metadata links. 

309 

310 Args: 

311 ta: Template arguments. 

312 md: Metadata. 

313 

314 Yields: 

315 Generators of XML elements, one for each link. 

316 """ 

317 

318 if md.metaLinks: 

319 for ml in md.metaLinks: 

320 yield meta_url_nested(ta, ml, 'MetadataURL') 

321 

322 

323def meta_url_nested(ta: server.TemplateArgs, ml: gws.MetadataLink, name: str): 

324 """Create a metadata link element in the nested format, with ``Format`` and ``OnlineResource``. 

325 

326 Args: 

327 ta: Template arguments. 

328 ml: Metadata link, may be empty. 

329 name: Element name. 

330 

331 Yields: 

332 The XML element, if the link is not empty. 

333 """ 

334 

335 if ml: 

336 yield tag(name, {'type': ml.type}, tag('Format', ml.format), online_resource(ta.url_for(ml.url))) 

337 

338 

339# Simple format (WFS 2) 

340# OGC 09-025r1 Table 11 

341# The xlink:href element shall be used to reference any metadata. 

342# The optional about attribute may be used to reference the aspect of the element which includes 

343# this wfs:MetadataURL element that this metadata provides more information about. 

344# (whatever that means) 

345 

346 

347def meta_links_simple(ta: server.TemplateArgs, md: gws.Metadata): 

348 """Create ``MetadataURL`` elements in the simple format for the metadata links. 

349 

350 Args: 

351 ta: Template arguments. 

352 md: Metadata. 

353 

354 Yields: 

355 Generators of XML elements, one for each link. 

356 """ 

357 

358 if md.metaLinks: 

359 for ml in md.metaLinks: 

360 yield meta_url_simple(ta, ml, 'MetadataURL') 

361 

362 

363def meta_url_simple(ta: server.TemplateArgs, ml: gws.MetadataLink, name: str): 

364 """Create a metadata link element in the simple format, with ``xlink:href`` and ``about`` attributes. 

365 

366 Args: 

367 ta: Template arguments. 

368 ml: Metadata link, may be empty. 

369 name: Element name. 

370 

371 Yields: 

372 The XML element, if the link is not empty. 

373 """ 

374 

375 if ml: 

376 yield tag(name, {'XLINK:href': ta.url_for(ml.url), 'about': ml.about}) 

377 

378 

379def wfs_feature_collection(ta: server.TemplateArgs): 

380 """Create a ``wfs:FeatureCollection`` element from the feature collection in the template arguments. 

381 

382 Args: 

383 ta: Template arguments. 

384 

385 Returns: 

386 The XML element. 

387 """ 

388 

389 return tag( 

390 'WFS:FeatureCollection', 

391 wfs_feature_collection_attributes(ta), 

392 [ 

393 tag( 

394 'WFS:member', 

395 tag( 

396 xmlx.namespace.full_name(m.layerCaps.featureName, m.layerCaps.xmlNamespace) if m.layerCaps else 'WFS:feature', 

397 {'GML:id': gml_format_uid(ta, m.feature.uid())}, 

398 wfs_feature_collection_member(ta, m), 

399 ), 

400 ) 

401 for m in ta.featureCollection.members 

402 ], 

403 ) 

404 

405 

406def wfs_value_collection(ta: server.TemplateArgs): 

407 """Create a ``wfs:ValueCollection`` element from the values in the feature collection. 

408 

409 Args: 

410 ta: Template arguments. 

411 

412 Returns: 

413 The XML element. 

414 """ 

415 

416 return tag( 

417 'WFS:ValueCollection', 

418 wfs_feature_collection_attributes(ta), 

419 [tag('WFS:member', gml_format_value(ta, val)) for val in ta.featureCollection.values], 

420 ) 

421 

422 

423def wfs_feature_collection_attributes(ta): 

424 """Return the attributes of a WFS feature or value collection. 

425 

426 Args: 

427 ta: Template arguments. 

428 

429 Returns: 

430 A dict with ``timeStamp``, ``numberMatched`` and ``numberReturned``. 

431 """ 

432 

433 return { 

434 'timeStamp': ta.featureCollection.timestamp, 

435 'numberMatched': ta.featureCollection.numMatched, 

436 'numberReturned': ta.featureCollection.numReturned, 

437 } 

438 

439 

440def wfs_feature_collection_member(ta: server.TemplateArgs, m: server.FeatureCollectionMember): 

441 """Create the attribute elements of a feature collection member. 

442 

443 Args: 

444 ta: Template arguments. 

445 m: Collection member. 

446 

447 Yields: 

448 XML elements, one for each attribute; the geometry comes last. 

449 """ 

450 

451 geom = None 

452 for name, val in m.feature.attributes.items(): 

453 if m.layerCaps: 

454 name = xmlx.namespace.full_name(name, m.layerCaps.xmlNamespace) 

455 if isinstance(val, gws.Shape): 

456 geom = tag(name, gml_format_value(ta, val)) 

457 else: 

458 yield tag(name, gml_format_value(ta, val)) 

459 # QGIS wants geometry as the last element 

460 if geom: 

461 yield geom 

462 

463 

464def gml_format_uid(ta: server.TemplateArgs, uid): 

465 """Format a feature uid as a valid ``gml:id``. 

466 

467 Args: 

468 ta: Template arguments. 

469 uid: Feature uid. 

470 

471 Returns: 

472 The uid, prefixed with ``_`` if it starts with a digit, or ``_`` if it is empty. 

473 """ 

474 

475 if not uid: 

476 return '_' 

477 s = str(uid) 

478 if s[0].isdigit(): 

479 return '_' + s 

480 return s 

481 

482 

483def gml_format_value(ta, val): 

484 """Format an attribute value for GML output. 

485 

486 Args: 

487 ta: Template arguments. 

488 val: Attribute value. 

489 

490 Returns: 

491 A string, or a GML element for shapes. 

492 """ 

493 

494 s, ok = xmlx.util.atom_to_string(val) 

495 if ok: 

496 return s 

497 if isinstance(val, gws.Shape): 

498 # NB Qgis wants inline gml xmlns for adhoc schemas 

499 return gws.lib.gml.shape_to_element( 

500 val, 

501 version=ta.gmlVersion, 

502 always_xy=ta.serviceRequest.alwaysXY, 

503 with_inline_xmlns=True, 

504 ) 

505 return str(val) 

506 

507 

508# http://inspire.ec.europa.eu/schemas/common/1.0/network.xsd 

509# Scenario 2: Mandatory (where appropriate) metadata elements not mapped to standard capabilities, 

510# plus mandatory language parameters, 

511# plus OPTIONAL MetadataUrl pointing to an INSPIRE Compliant ISO metadata document 

512 

513 

514def inspire_extended_capabilities(ta: server.TemplateArgs): 

515 """Create the INSPIRE extended capabilities elements from the service metadata. 

516 

517 Args: 

518 ta: Template arguments. 

519 

520 Returns: 

521 A list of XML elements. 

522 """ 

523 

524 md = ta.service.metadata 

525 return [ 

526 tag( 

527 'INSPIRE_COMMON:ResourceLocator', 

528 tag('INSPIRE_COMMON:URL', ta.serviceUrl), 

529 tag('INSPIRE_COMMON:MediaType', 'application/xml'), 

530 ), 

531 tag('INSPIRE_COMMON:ResourceType', md.inspireResourceType), 

532 tag('INSPIRE_COMMON:TemporalReference/INSPIRE_COMMON:DateOfPublication', iso_date(md.dateCreated)), 

533 tag( 

534 'INSPIRE_COMMON:Conformity', 

535 tag( 

536 'INSPIRE_COMMON:Specification', 

537 # {'XSI:type': 'inspire_common:citationInspireInteroperabilityRegulation'}, 

538 tag( 

539 'INSPIRE_COMMON:Title', 

540 'COMMISSION REGULATION (EU) No 1089/2010 of 23 November 2010 implementing Directive 2007/2/EC of the European Parliament and of the Council as regards interoperability of spatial data sets and services', 

541 ), 

542 tag('INSPIRE_COMMON:DateOfPublication', '2010-12-08'), 

543 tag('INSPIRE_COMMON:URI', 'OJ:L:2010:323:0011:0102:EN:PDF'), 

544 tag( 

545 'INSPIRE_COMMON:ResourceLocator', 

546 tag('INSPIRE_COMMON:URL', 'http://eur-lex.europa.eu/LexUriServ/LexUriServ.do?uri=OJ:L:2010:323:0011:0102:EN:PDF'), 

547 tag('INSPIRE_COMMON:MediaType', 'application/pdf'), 

548 ), 

549 ), 

550 tag('INSPIRE_COMMON:Degree', md.inspireDegreeOfConformity), 

551 ), 

552 tag( 

553 'INSPIRE_COMMON:MetadataPointOfContact', 

554 tag('INSPIRE_COMMON:OrganisationName', md.contactOrganization), 

555 tag('INSPIRE_COMMON:EmailAddress', md.contactEmail), 

556 ), 

557 tag('INSPIRE_COMMON:MetadataDate', iso_date(md.dateCreated)), 

558 tag('INSPIRE_COMMON:SpatialDataServiceType', md.inspireSpatialDataServiceType), 

559 tag('INSPIRE_COMMON:MandatoryKeyword/INSPIRE_COMMON:KeywordValue', md.inspireMandatoryKeyword), 

560 tag( 

561 'INSPIRE_COMMON:Keyword', 

562 tag( 

563 'INSPIRE_COMMON:OriginatingControlledVocabulary', 

564 tag('INSPIRE_COMMON:Title', 'INSPIRE themes'), 

565 tag('INSPIRE_COMMON:DateOfPublication', '2008-06-01'), 

566 ), 

567 tag('INSPIRE_COMMON:KeywordValue', md.inspireThemeNameEn), 

568 ), 

569 tag( 

570 'INSPIRE_COMMON:SupportedLanguages', 

571 tag('INSPIRE_COMMON:DefaultLanguage/INSPIRE_COMMON:Language', md.languageBib), 

572 tag('INSPIRE_COMMON:SupportedLanguage/INSPIRE_COMMON:Language', md.languageBib), 

573 ), 

574 tag('INSPIRE_COMMON:ResponseLanguage/INSPIRE_COMMON:Language', md.languageBib), 

575 ] 

576 

577 

578def coord_dms(n): 

579 """Format a coordinate in degrees, with the default precision for degrees. 

580 

581 Args: 

582 n: Coordinate value. 

583 

584 Returns: 

585 The formatted string. 

586 """ 

587 

588 prec = gws.lib.uom.DEFAULT_PRECISION[gws.Uom.deg] 

589 return f'{round(n, prec):.{prec}f}' 

590 

591 

592def coord_m(n): 

593 """Format a coordinate in meters, with the default precision for meters. 

594 

595 Args: 

596 n: Coordinate value. 

597 

598 Returns: 

599 The formatted string. 

600 """ 

601 

602 prec = gws.lib.uom.DEFAULT_PRECISION[gws.Uom.m] 

603 return f'{round(n, prec):.{prec}f}' 

604 

605 

606def iso_date(d): 

607 """Format a date as an ISO date string. 

608 

609 Args: 

610 d: Date value or string. 

611 

612 Returns: 

613 The ISO date, or an empty string if the value cannot be parsed. 

614 """ 

615 

616 dd = dtx.parse(d) 

617 return dtx.to_iso_date_string(dd) if dd else '' 

618 

619 

620def iso_datetime(d): 

621 """Format a date as an ISO date-time string with time zone. 

622 

623 Args: 

624 d: Date value or string. 

625 

626 Returns: 

627 The ISO date-time, or an empty string if the value cannot be parsed. 

628 """ 

629 

630 dd = dtx.parse(d) 

631 return dtx.to_iso_string(dd, with_tz=':') if dd else '' 

632 

633 

634def namespaces_from_caps(ta: server.TemplateArgs) -> list[gws.XmlNamespace]: 

635 """Return the feature type namespaces, to declare in documents that reference feature types by QName. 

636 

637 Args: 

638 ta: Template arguments. 

639 

640 Returns: 

641 The distinct namespaces of the layers in ``layerCapsList``. 

642 """ 

643 

644 d = {} 

645 for lc in ta.layerCapsList: 

646 if lc.xmlNamespace: 

647 d[lc.xmlNamespace.uri] = lc.xmlNamespace 

648 return list(d.values()) 

649 

650 

651def to_xml_response( 

652 ta: server.TemplateArgs, 

653 el: gws.XmlElement, 

654 default_namespace: Optional[gws.XmlNamespace] = None, 

655 extra_namespaces: Optional[list[gws.XmlNamespace]] = None, 

656 doctype: Optional[str] = None, 

657) -> gws.ContentResponse: 

658 """Create an XML response. 

659 

660 For SOAP requests, the element is wrapped in a SOAP envelope. 

661 

662 Args: 

663 ta: Template arguments. 

664 el: Root element. 

665 default_namespace: Default namespace of the document. 

666 extra_namespaces: Namespaces to declare on the root in addition to those used by elements and attributes, 

667 e.g. for QName values. 

668 doctype: DTD for DTD-based formats (WMS 1.1.x). These have no namespace declarations on the root; 

669 ``xlink`` is declared inline where it is used (as per the DTD's #FIXED xmlns:xlink). 

670 

671 Returns: 

672 The XML response. 

673 """ 

674 

675 if extra_namespaces: 

676 el.declare(*extra_namespaces) 

677 

678 if doctype: 

679 xlink = xmlx.namespace.c.XLINK 

680 xlink_uri = '{' + xlink.uri + '}' 

681 for e in el.iter(): 

682 if any(k.startswith(xlink_uri) for k in e.attrib): 

683 e.declare(xlink) 

684 

685 if ta.serviceRequest.isSoap: 

686 el = tag('SOAP:Envelope', tag('SOAP:Header'), tag('SOAP:Body', el)) 

687 

688 opts = gws.XmlOptions( 

689 doctype=doctype, 

690 defaultNamespace=default_namespace, 

691 withNamespaceDeclarations=not doctype, 

692 withSchemaLocations=not doctype, 

693 withXmlDeclaration=True, 

694 customNamespacePrefixes=ta.serviceRequest.customNamespacePrefixes, 

695 ) 

696 

697 return cast(service.Object, ta.serviceRequest.service).xml_response(el, opts)