@honua/sdk-esri-compat 0.0.6-alpha.0
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.
- package/README.md +5 -0
- package/contract/edit-session.d.ts +203 -0
- package/contract/edit-session.d.ts.map +1 -0
- package/contract/edit-session.js +717 -0
- package/contract/index.d.ts +20 -0
- package/contract/index.d.ts.map +1 -0
- package/contract/index.js +15 -0
- package/contract/jobs.d.ts +115 -0
- package/contract/jobs.d.ts.map +1 -0
- package/contract/jobs.js +22 -0
- package/contract/source.d.ts +138 -0
- package/contract/source.d.ts.map +1 -0
- package/contract/source.js +3364 -0
- package/contract/spatial-aggregation.d.ts +312 -0
- package/contract/spatial-aggregation.d.ts.map +1 -0
- package/contract/spatial-aggregation.js +196 -0
- package/contract/types.d.ts +542 -0
- package/contract/types.d.ts.map +1 -0
- package/contract/types.js +171 -0
- package/core/batch.d.ts +37 -0
- package/core/batch.d.ts.map +1 -0
- package/core/batch.js +78 -0
- package/core/cache-state.d.ts +76 -0
- package/core/cache-state.d.ts.map +1 -0
- package/core/cache-state.js +84 -0
- package/core/client.d.ts +289 -0
- package/core/client.d.ts.map +1 -0
- package/core/client.js +2625 -0
- package/core/errors.d.ts +68 -0
- package/core/errors.d.ts.map +1 -0
- package/core/errors.js +116 -0
- package/core/grpc-adapter.d.ts +23 -0
- package/core/grpc-adapter.d.ts.map +1 -0
- package/core/grpc-adapter.js +704 -0
- package/core/odata.d.ts +347 -0
- package/core/odata.d.ts.map +1 -0
- package/core/odata.js +837 -0
- package/core/ogc-conformance.d.ts +35 -0
- package/core/ogc-conformance.d.ts.map +1 -0
- package/core/ogc-conformance.js +121 -0
- package/core/ogc-maps.d.ts +45 -0
- package/core/ogc-maps.d.ts.map +1 -0
- package/core/ogc-maps.js +53 -0
- package/core/ogc-processes.d.ts +92 -0
- package/core/ogc-processes.d.ts.map +1 -0
- package/core/ogc-processes.js +325 -0
- package/core/ogc-tiles.d.ts +55 -0
- package/core/ogc-tiles.d.ts.map +1 -0
- package/core/ogc-tiles.js +81 -0
- package/core/pbf-decoder.d.ts +28 -0
- package/core/pbf-decoder.d.ts.map +1 -0
- package/core/pbf-decoder.js +627 -0
- package/core/query-builder.d.ts +261 -0
- package/core/query-builder.d.ts.map +1 -0
- package/core/query-builder.js +538 -0
- package/core/spatial-filter.d.ts +93 -0
- package/core/spatial-filter.d.ts.map +1 -0
- package/core/spatial-filter.js +153 -0
- package/core/stac.d.ts +43 -0
- package/core/stac.d.ts.map +1 -0
- package/core/stac.js +116 -0
- package/core/surfaces.d.ts +464 -0
- package/core/surfaces.d.ts.map +1 -0
- package/core/surfaces.js +1322 -0
- package/core/types.d.ts +1238 -0
- package/core/types.d.ts.map +1 -0
- package/core/types.js +13 -0
- package/core/wfs-capabilities.d.ts +143 -0
- package/core/wfs-capabilities.d.ts.map +1 -0
- package/core/wfs-capabilities.js +478 -0
- package/core/wfs-filter.d.ts +118 -0
- package/core/wfs-filter.d.ts.map +1 -0
- package/core/wfs-filter.js +586 -0
- package/core/wfs.d.ts +219 -0
- package/core/wfs.d.ts.map +1 -0
- package/core/wfs.js +406 -0
- package/core/wms-capabilities.d.ts +110 -0
- package/core/wms-capabilities.d.ts.map +1 -0
- package/core/wms-capabilities.js +566 -0
- package/core/wms-types.d.ts +128 -0
- package/core/wms-types.d.ts.map +1 -0
- package/core/wms-types.js +32 -0
- package/core/wms.d.ts +108 -0
- package/core/wms.d.ts.map +1 -0
- package/core/wms.js +182 -0
- package/core/wmts-capabilities.d.ts +82 -0
- package/core/wmts-capabilities.d.ts.map +1 -0
- package/core/wmts-capabilities.js +409 -0
- package/core/wmts.d.ts +80 -0
- package/core/wmts.d.ts.map +1 -0
- package/core/wmts.js +173 -0
- package/esri-compat/basemap-gallery.d.ts +38 -0
- package/esri-compat/basemap-gallery.d.ts.map +1 -0
- package/esri-compat/basemap-gallery.js +159 -0
- package/esri-compat/basemap-layer-list.d.ts +35 -0
- package/esri-compat/basemap-layer-list.d.ts.map +1 -0
- package/esri-compat/basemap-layer-list.js +139 -0
- package/esri-compat/basemap.d.ts +32 -0
- package/esri-compat/basemap.d.ts.map +1 -0
- package/esri-compat/basemap.js +84 -0
- package/esri-compat/bookmarks.d.ts +36 -0
- package/esri-compat/bookmarks.d.ts.map +1 -0
- package/esri-compat/bookmarks.js +108 -0
- package/esri-compat/class-breaks-renderer.d.ts +50 -0
- package/esri-compat/class-breaks-renderer.d.ts.map +1 -0
- package/esri-compat/class-breaks-renderer.js +164 -0
- package/esri-compat/color.d.ts +22 -0
- package/esri-compat/color.d.ts.map +1 -0
- package/esri-compat/color.js +106 -0
- package/esri-compat/controls.d.ts +174 -0
- package/esri-compat/controls.d.ts.map +1 -0
- package/esri-compat/controls.js +479 -0
- package/esri-compat/coordinate-conversion.d.ts +41 -0
- package/esri-compat/coordinate-conversion.d.ts.map +1 -0
- package/esri-compat/coordinate-conversion.js +125 -0
- package/esri-compat/directions.d.ts +45 -0
- package/esri-compat/directions.d.ts.map +1 -0
- package/esri-compat/directions.js +123 -0
- package/esri-compat/editor.d.ts +47 -0
- package/esri-compat/editor.d.ts.map +1 -0
- package/esri-compat/editor.js +170 -0
- package/esri-compat/esri-config.d.ts +16 -0
- package/esri-compat/esri-config.d.ts.map +1 -0
- package/esri-compat/esri-config.js +23 -0
- package/esri-compat/esri-request.d.ts +17 -0
- package/esri-compat/esri-request.d.ts.map +1 -0
- package/esri-compat/esri-request.js +66 -0
- package/esri-compat/event-bus.d.ts +106 -0
- package/esri-compat/event-bus.d.ts.map +1 -0
- package/esri-compat/event-bus.js +90 -0
- package/esri-compat/expand.d.ts +36 -0
- package/esri-compat/expand.d.ts.map +1 -0
- package/esri-compat/expand.js +97 -0
- package/esri-compat/extent.d.ts +43 -0
- package/esri-compat/extent.d.ts.map +1 -0
- package/esri-compat/extent.js +140 -0
- package/esri-compat/feature-form.d.ts +64 -0
- package/esri-compat/feature-form.d.ts.map +1 -0
- package/esri-compat/feature-form.js +137 -0
- package/esri-compat/feature-layer.d.ts +173 -0
- package/esri-compat/feature-layer.d.ts.map +1 -0
- package/esri-compat/feature-layer.js +655 -0
- package/esri-compat/feature-set.d.ts +32 -0
- package/esri-compat/feature-set.d.ts.map +1 -0
- package/esri-compat/feature-set.js +80 -0
- package/esri-compat/feature-table.d.ts +141 -0
- package/esri-compat/feature-table.d.ts.map +1 -0
- package/esri-compat/feature-table.js +500 -0
- package/esri-compat/feature-templates.d.ts +41 -0
- package/esri-compat/feature-templates.d.ts.map +1 -0
- package/esri-compat/feature-templates.js +97 -0
- package/esri-compat/feature.d.ts +33 -0
- package/esri-compat/feature.d.ts.map +1 -0
- package/esri-compat/feature.js +83 -0
- package/esri-compat/graphic.d.ts +46 -0
- package/esri-compat/graphic.d.ts.map +1 -0
- package/esri-compat/graphic.js +103 -0
- package/esri-compat/graphics-layer.d.ts +49 -0
- package/esri-compat/graphics-layer.d.ts.map +1 -0
- package/esri-compat/graphics-layer.js +185 -0
- package/esri-compat/group-layer.d.ts +48 -0
- package/esri-compat/group-layer.d.ts.map +1 -0
- package/esri-compat/group-layer.js +212 -0
- package/esri-compat/identify.d.ts +71 -0
- package/esri-compat/identify.d.ts.map +1 -0
- package/esri-compat/identify.js +299 -0
- package/esri-compat/identity-manager.d.ts +31 -0
- package/esri-compat/identity-manager.d.ts.map +1 -0
- package/esri-compat/identity-manager.js +115 -0
- package/esri-compat/label-class.d.ts +31 -0
- package/esri-compat/label-class.d.ts.map +1 -0
- package/esri-compat/label-class.js +102 -0
- package/esri-compat/layer-list.d.ts +77 -0
- package/esri-compat/layer-list.d.ts.map +1 -0
- package/esri-compat/layer-list.js +342 -0
- package/esri-compat/legend.d.ts +53 -0
- package/esri-compat/legend.d.ts.map +1 -0
- package/esri-compat/legend.js +254 -0
- package/esri-compat/map-image-layer.d.ts +162 -0
- package/esri-compat/map-image-layer.d.ts.map +1 -0
- package/esri-compat/map-image-layer.js +704 -0
- package/esri-compat/map-view.d.ts +315 -0
- package/esri-compat/map-view.d.ts.map +1 -0
- package/esri-compat/map-view.js +1110 -0
- package/esri-compat/map.d.ts +47 -0
- package/esri-compat/map.d.ts.map +1 -0
- package/esri-compat/map.js +209 -0
- package/esri-compat/measurement-2d.d.ts +61 -0
- package/esri-compat/measurement-2d.d.ts.map +1 -0
- package/esri-compat/measurement-2d.js +184 -0
- package/esri-compat/measurement.d.ts +45 -0
- package/esri-compat/measurement.d.ts.map +1 -0
- package/esri-compat/measurement.js +193 -0
- package/esri-compat/oauth-info.d.ts +35 -0
- package/esri-compat/oauth-info.d.ts.map +1 -0
- package/esri-compat/oauth-info.js +115 -0
- package/esri-compat/picture-marker-symbol.d.ts +35 -0
- package/esri-compat/picture-marker-symbol.d.ts.map +1 -0
- package/esri-compat/picture-marker-symbol.js +126 -0
- package/esri-compat/point.d.ts +31 -0
- package/esri-compat/point.d.ts.map +1 -0
- package/esri-compat/point.js +102 -0
- package/esri-compat/polygon.d.ts +31 -0
- package/esri-compat/polygon.d.ts.map +1 -0
- package/esri-compat/polygon.js +107 -0
- package/esri-compat/polyline.d.ts +31 -0
- package/esri-compat/polyline.d.ts.map +1 -0
- package/esri-compat/polyline.js +107 -0
- package/esri-compat/popup-template.d.ts +55 -0
- package/esri-compat/popup-template.d.ts.map +1 -0
- package/esri-compat/popup-template.js +128 -0
- package/esri-compat/popup.d.ts +54 -0
- package/esri-compat/popup.d.ts.map +1 -0
- package/esri-compat/popup.js +312 -0
- package/esri-compat/print.d.ts +47 -0
- package/esri-compat/print.d.ts.map +1 -0
- package/esri-compat/print.js +105 -0
- package/esri-compat/query.d.ts +46 -0
- package/esri-compat/query.d.ts.map +1 -0
- package/esri-compat/query.js +119 -0
- package/esri-compat/reactive-utils.d.ts +23 -0
- package/esri-compat/reactive-utils.d.ts.map +1 -0
- package/esri-compat/reactive-utils.js +178 -0
- package/esri-compat/request.d.ts +41 -0
- package/esri-compat/request.d.ts.map +1 -0
- package/esri-compat/request.js +253 -0
- package/esri-compat/route-layer.d.ts +59 -0
- package/esri-compat/route-layer.d.ts.map +1 -0
- package/esri-compat/route-layer.js +223 -0
- package/esri-compat/route-task.d.ts +88 -0
- package/esri-compat/route-task.d.ts.map +1 -0
- package/esri-compat/route-task.js +225 -0
- package/esri-compat/scene-view.d.ts +41 -0
- package/esri-compat/scene-view.d.ts.map +1 -0
- package/esri-compat/scene-view.js +68 -0
- package/esri-compat/search.d.ts +112 -0
- package/esri-compat/search.d.ts.map +1 -0
- package/esri-compat/search.js +820 -0
- package/esri-compat/simple-fill-symbol.d.ts +27 -0
- package/esri-compat/simple-fill-symbol.d.ts.map +1 -0
- package/esri-compat/simple-fill-symbol.js +85 -0
- package/esri-compat/simple-line-symbol.d.ts +27 -0
- package/esri-compat/simple-line-symbol.d.ts.map +1 -0
- package/esri-compat/simple-line-symbol.js +85 -0
- package/esri-compat/simple-marker-symbol.d.ts +29 -0
- package/esri-compat/simple-marker-symbol.d.ts.map +1 -0
- package/esri-compat/simple-marker-symbol.js +92 -0
- package/esri-compat/simple-renderer.d.ts +29 -0
- package/esri-compat/simple-renderer.d.ts.map +1 -0
- package/esri-compat/simple-renderer.js +92 -0
- package/esri-compat/sketch.d.ts +64 -0
- package/esri-compat/sketch.d.ts.map +1 -0
- package/esri-compat/sketch.js +227 -0
- package/esri-compat/spatial-reference.d.ts +31 -0
- package/esri-compat/spatial-reference.d.ts.map +1 -0
- package/esri-compat/spatial-reference.js +102 -0
- package/esri-compat/swipe.d.ts +34 -0
- package/esri-compat/swipe.d.ts.map +1 -0
- package/esri-compat/swipe.js +93 -0
- package/esri-compat/table-list.d.ts +36 -0
- package/esri-compat/table-list.d.ts.map +1 -0
- package/esri-compat/table-list.js +126 -0
- package/esri-compat/text-symbol.d.ts +37 -0
- package/esri-compat/text-symbol.d.ts.map +1 -0
- package/esri-compat/text-symbol.js +123 -0
- package/esri-compat/tile-layer.d.ts +52 -0
- package/esri-compat/tile-layer.d.ts.map +1 -0
- package/esri-compat/tile-layer.js +167 -0
- package/esri-compat/time-slider.d.ts +70 -0
- package/esri-compat/time-slider.d.ts.map +1 -0
- package/esri-compat/time-slider.js +208 -0
- package/esri-compat/track.d.ts +51 -0
- package/esri-compat/track.d.ts.map +1 -0
- package/esri-compat/track.js +174 -0
- package/esri-compat/unique-value-renderer.d.ts +41 -0
- package/esri-compat/unique-value-renderer.d.ts.map +1 -0
- package/esri-compat/unique-value-renderer.js +127 -0
- package/esri-compat/url.d.ts +12 -0
- package/esri-compat/url.d.ts.map +1 -0
- package/esri-compat/url.js +54 -0
- package/esri-compat/web-map.d.ts +14 -0
- package/esri-compat/web-map.d.ts.map +1 -0
- package/esri-compat/web-map.js +24 -0
- package/gen/honua/v1/feature_service_pb.d.ts +918 -0
- package/gen/honua/v1/feature_service_pb.d.ts.map +1 -0
- package/gen/honua/v1/feature_service_pb.js +375 -0
- package/index.d.ts +130 -0
- package/index.js +65 -0
- package/package.json +22 -0
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WMS 1.3.0 Capabilities parser. Hand-rolled, narrow-scope text-walker
|
|
3
|
+
* over a `WMS_Capabilities` document. Returns a typed shape (layers,
|
|
4
|
+
* styles, bbox-per-CRS, dimensions, advertised formats) that the SDK
|
|
5
|
+
* can route through the canonical `Source` surface without leaking XML
|
|
6
|
+
* nodes into its public API.
|
|
7
|
+
*
|
|
8
|
+
* The parser is closed-schema (WMS 1.3 + the ISO 19128 service-metadata
|
|
9
|
+
* subset honua-server emits) and tolerates missing optional nodes
|
|
10
|
+
* gracefully. Vendor-extension elements outside the named set are
|
|
11
|
+
* ignored. A `HonuaWmsCapabilitiesParseError` (extends `HonuaError`) is
|
|
12
|
+
* thrown when a required root or top-level node is missing.
|
|
13
|
+
*
|
|
14
|
+
* @module
|
|
15
|
+
*/
|
|
16
|
+
/**
|
|
17
|
+
* Top-level WMS Capabilities surface.
|
|
18
|
+
*/
|
|
19
|
+
export interface WmsCapabilities {
|
|
20
|
+
/** WMS protocol version. honua-server advertises `1.3.0`. */
|
|
21
|
+
version: string;
|
|
22
|
+
service: WmsCapabilitiesService;
|
|
23
|
+
layers: ReadonlyArray<WmsCapabilityLayer>;
|
|
24
|
+
formats: WmsCapabilitiesFormats;
|
|
25
|
+
request: WmsCapabilitiesRequestSupport;
|
|
26
|
+
}
|
|
27
|
+
export interface WmsCapabilitiesService {
|
|
28
|
+
title?: string;
|
|
29
|
+
abstract?: string;
|
|
30
|
+
}
|
|
31
|
+
export interface WmsCapabilitiesFormats {
|
|
32
|
+
/** Image MIME types accepted by `GetMap`. */
|
|
33
|
+
map: readonly string[];
|
|
34
|
+
/** Info-format MIME types accepted by `GetFeatureInfo`. */
|
|
35
|
+
featureInfo: readonly string[];
|
|
36
|
+
/** Image MIME types accepted by `GetLegendGraphic`. */
|
|
37
|
+
legend: readonly string[];
|
|
38
|
+
}
|
|
39
|
+
export interface WmsCapabilitiesRequestSupport {
|
|
40
|
+
/** Capability advertises a `GetFeatureInfo` request element. */
|
|
41
|
+
getFeatureInfo: boolean;
|
|
42
|
+
/** Capability advertises a `GetLegendGraphic` request element. */
|
|
43
|
+
getLegendGraphic: boolean;
|
|
44
|
+
}
|
|
45
|
+
export interface WmsCapabilityLayer {
|
|
46
|
+
name: string;
|
|
47
|
+
title?: string;
|
|
48
|
+
abstract?: string;
|
|
49
|
+
/** CRS codes advertised on this layer (and inherited from ancestors). */
|
|
50
|
+
crs: readonly string[];
|
|
51
|
+
/** Bounding boxes per CRS. Multiple entries permitted. */
|
|
52
|
+
bbox: ReadonlyArray<WmsCapabilityBoundingBox>;
|
|
53
|
+
/** Styles advertised on this layer. The first entry is the default. */
|
|
54
|
+
styles: ReadonlyArray<WmsCapabilityStyle>;
|
|
55
|
+
/** Dimensions (`time`, `elevation`, vendor names). */
|
|
56
|
+
dimensions: ReadonlyArray<WmsCapabilityDimension>;
|
|
57
|
+
queryable: boolean;
|
|
58
|
+
/** Sub-layers nested under this entry. */
|
|
59
|
+
children: ReadonlyArray<WmsCapabilityLayer>;
|
|
60
|
+
}
|
|
61
|
+
export interface WmsCapabilityBoundingBox {
|
|
62
|
+
crs: string;
|
|
63
|
+
minx: number;
|
|
64
|
+
miny: number;
|
|
65
|
+
maxx: number;
|
|
66
|
+
maxy: number;
|
|
67
|
+
}
|
|
68
|
+
export interface WmsCapabilityStyle {
|
|
69
|
+
name: string;
|
|
70
|
+
title?: string;
|
|
71
|
+
/** Externally-served legend URL (`OnlineResource@xlink:href`) when advertised. */
|
|
72
|
+
legendUrl?: string;
|
|
73
|
+
}
|
|
74
|
+
export interface WmsCapabilityDimension {
|
|
75
|
+
name: string;
|
|
76
|
+
units?: string;
|
|
77
|
+
default?: string;
|
|
78
|
+
/** Discrete values; for time this is typically a comma-separated list. */
|
|
79
|
+
values: readonly string[];
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Thrown when WMS Capabilities XML is missing required root structure or
|
|
83
|
+
* is otherwise malformed in a way that prevents extracting the typed
|
|
84
|
+
* shape above. Recoverable per-node gaps (missing `<Title>`,
|
|
85
|
+
* `<Abstract>`, etc.) are tolerated and return defaults.
|
|
86
|
+
*/
|
|
87
|
+
export declare class HonuaWmsCapabilitiesParseError extends Error {
|
|
88
|
+
constructor(message: string);
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Parse a `WMS_Capabilities` text body into the typed `WmsCapabilities`
|
|
92
|
+
* shape. The input is the raw XML string returned by `requestText`; the
|
|
93
|
+
* parser walks it once with a small named-element matcher and tolerates
|
|
94
|
+
* vendor extensions (unknown elements are skipped).
|
|
95
|
+
*/
|
|
96
|
+
export declare function parseWmsCapabilities(xml: string): WmsCapabilities;
|
|
97
|
+
/**
|
|
98
|
+
* Walk a parsed `WmsCapabilities` tree and yield every named layer
|
|
99
|
+
* (skipping container layers without a `name`). Useful for descriptor
|
|
100
|
+
* resolution: callers want to bind a `LAYER` name without re-walking
|
|
101
|
+
* the parent hierarchy.
|
|
102
|
+
*/
|
|
103
|
+
export declare function iterateWmsLayers(capabilities: WmsCapabilities): Generator<WmsCapabilityLayer>;
|
|
104
|
+
/**
|
|
105
|
+
* Find the first named layer with a matching name, or `undefined` if the
|
|
106
|
+
* capabilities document does not advertise it. Walks both root layers and
|
|
107
|
+
* children. Intended for the `HonuaWms.layer(name)` resolver path.
|
|
108
|
+
*/
|
|
109
|
+
export declare function findWmsLayer(capabilities: WmsCapabilities, name: string): WmsCapabilityLayer | undefined;
|
|
110
|
+
//# sourceMappingURL=wms-capabilities.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wms-capabilities.d.ts","sourceRoot":"","sources":["../../../src/core/wms-capabilities.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,6DAA6D;IAC7D,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,sBAAsB,CAAC;IAChC,MAAM,EAAE,aAAa,CAAC,kBAAkB,CAAC,CAAC;IAC1C,OAAO,EAAE,sBAAsB,CAAC;IAChC,OAAO,EAAE,6BAA6B,CAAC;CACxC;AAED,MAAM,WAAW,sBAAsB;IACrC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,sBAAsB;IACrC,6CAA6C;IAC7C,GAAG,EAAE,SAAS,MAAM,EAAE,CAAC;IACvB,2DAA2D;IAC3D,WAAW,EAAE,SAAS,MAAM,EAAE,CAAC;IAC/B,uDAAuD;IACvD,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC;CAC3B;AAED,MAAM,WAAW,6BAA6B;IAC5C,gEAAgE;IAChE,cAAc,EAAE,OAAO,CAAC;IACxB,kEAAkE;IAClE,gBAAgB,EAAE,OAAO,CAAC;CAC3B;AAED,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,yEAAyE;IACzE,GAAG,EAAE,SAAS,MAAM,EAAE,CAAC;IACvB,0DAA0D;IAC1D,IAAI,EAAE,aAAa,CAAC,wBAAwB,CAAC,CAAC;IAC9C,uEAAuE;IACvE,MAAM,EAAE,aAAa,CAAC,kBAAkB,CAAC,CAAC;IAC1C,sDAAsD;IACtD,UAAU,EAAE,aAAa,CAAC,sBAAsB,CAAC,CAAC;IAClD,SAAS,EAAE,OAAO,CAAC;IACnB,0CAA0C;IAC1C,QAAQ,EAAE,aAAa,CAAC,kBAAkB,CAAC,CAAC;CAC7C;AAED,MAAM,WAAW,wBAAwB;IACvC,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,kFAAkF;IAClF,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,sBAAsB;IACrC,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,0EAA0E;IAC1E,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC;CAC3B;AAED;;;;;GAKG;AACH,qBAAa,8BAA+B,SAAQ,KAAK;gBACpC,OAAO,EAAE,MAAM;CAInC;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,MAAM,GAAG,eAAe,CAgCjE;AAueD;;;;;GAKG;AACH,wBAAiB,gBAAgB,CAAC,YAAY,EAAE,eAAe,GAAG,SAAS,CAAC,kBAAkB,CAAC,CAU9F;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,YAAY,EAAE,eAAe,EAAE,IAAI,EAAE,MAAM,GAAG,kBAAkB,GAAG,SAAS,CAKxG"}
|
|
@@ -0,0 +1,566 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WMS 1.3.0 Capabilities parser. Hand-rolled, narrow-scope text-walker
|
|
3
|
+
* over a `WMS_Capabilities` document. Returns a typed shape (layers,
|
|
4
|
+
* styles, bbox-per-CRS, dimensions, advertised formats) that the SDK
|
|
5
|
+
* can route through the canonical `Source` surface without leaking XML
|
|
6
|
+
* nodes into its public API.
|
|
7
|
+
*
|
|
8
|
+
* The parser is closed-schema (WMS 1.3 + the ISO 19128 service-metadata
|
|
9
|
+
* subset honua-server emits) and tolerates missing optional nodes
|
|
10
|
+
* gracefully. Vendor-extension elements outside the named set are
|
|
11
|
+
* ignored. A `HonuaWmsCapabilitiesParseError` (extends `HonuaError`) is
|
|
12
|
+
* thrown when a required root or top-level node is missing.
|
|
13
|
+
*
|
|
14
|
+
* @module
|
|
15
|
+
*/
|
|
16
|
+
/**
|
|
17
|
+
* Thrown when WMS Capabilities XML is missing required root structure or
|
|
18
|
+
* is otherwise malformed in a way that prevents extracting the typed
|
|
19
|
+
* shape above. Recoverable per-node gaps (missing `<Title>`,
|
|
20
|
+
* `<Abstract>`, etc.) are tolerated and return defaults.
|
|
21
|
+
*/
|
|
22
|
+
export class HonuaWmsCapabilitiesParseError extends Error {
|
|
23
|
+
constructor(message) {
|
|
24
|
+
super(message);
|
|
25
|
+
this.name = "HonuaWmsCapabilitiesParseError";
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Parse a `WMS_Capabilities` text body into the typed `WmsCapabilities`
|
|
30
|
+
* shape. The input is the raw XML string returned by `requestText`; the
|
|
31
|
+
* parser walks it once with a small named-element matcher and tolerates
|
|
32
|
+
* vendor extensions (unknown elements are skipped).
|
|
33
|
+
*/
|
|
34
|
+
export function parseWmsCapabilities(xml) {
|
|
35
|
+
if (typeof xml !== "string" || xml.length === 0) {
|
|
36
|
+
throw new HonuaWmsCapabilitiesParseError("WMS Capabilities body is empty");
|
|
37
|
+
}
|
|
38
|
+
const root = findElement(xml, 0, "WMS_Capabilities") ?? findElement(xml, 0, "WMT_MS_Capabilities");
|
|
39
|
+
if (!root) {
|
|
40
|
+
throw new HonuaWmsCapabilitiesParseError("missing <WMS_Capabilities> root element (received WMS Capabilities XML?)");
|
|
41
|
+
}
|
|
42
|
+
const version = readAttribute(root.openTag, "version") ?? "1.3.0";
|
|
43
|
+
const service = parseServiceMetadata(root.inner);
|
|
44
|
+
const capabilityNode = findElement(root.inner, 0, "Capability");
|
|
45
|
+
const requestNode = capabilityNode ? findElement(capabilityNode.inner, 0, "Request") : undefined;
|
|
46
|
+
const formats = parseFormats(requestNode?.inner ?? "");
|
|
47
|
+
const requestSupport = {
|
|
48
|
+
getFeatureInfo: requestNode ? findElement(requestNode.inner, 0, "GetFeatureInfo") !== undefined : false,
|
|
49
|
+
getLegendGraphic: requestNode ? findElement(requestNode.inner, 0, "GetLegendGraphic") !== undefined : false,
|
|
50
|
+
};
|
|
51
|
+
const layers = [];
|
|
52
|
+
if (capabilityNode) {
|
|
53
|
+
for (const layer of iterChildLayers(capabilityNode.inner, [])) {
|
|
54
|
+
layers.push(layer);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return {
|
|
58
|
+
version,
|
|
59
|
+
service,
|
|
60
|
+
layers,
|
|
61
|
+
formats,
|
|
62
|
+
request: requestSupport,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
function parseServiceMetadata(capabilityXml) {
|
|
66
|
+
const node = findElement(capabilityXml, 0, "Service");
|
|
67
|
+
if (!node)
|
|
68
|
+
return {};
|
|
69
|
+
const out = {};
|
|
70
|
+
const title = readTextElement(node.inner, "Title");
|
|
71
|
+
if (title !== undefined)
|
|
72
|
+
out.title = title;
|
|
73
|
+
const abstract = readTextElement(node.inner, "Abstract");
|
|
74
|
+
if (abstract !== undefined)
|
|
75
|
+
out.abstract = abstract;
|
|
76
|
+
return out;
|
|
77
|
+
}
|
|
78
|
+
function parseFormats(requestInnerXml) {
|
|
79
|
+
const map = collectFormats(requestInnerXml, "GetMap");
|
|
80
|
+
const featureInfo = collectFormats(requestInnerXml, "GetFeatureInfo");
|
|
81
|
+
const legend = collectFormats(requestInnerXml, "GetLegendGraphic");
|
|
82
|
+
return { map, featureInfo, legend };
|
|
83
|
+
}
|
|
84
|
+
function collectFormats(requestInnerXml, requestName) {
|
|
85
|
+
const node = findElement(requestInnerXml, 0, requestName);
|
|
86
|
+
if (!node)
|
|
87
|
+
return [];
|
|
88
|
+
const formats = [];
|
|
89
|
+
let cursor = 0;
|
|
90
|
+
while (cursor < node.inner.length) {
|
|
91
|
+
const fmt = findElement(node.inner, cursor, "Format");
|
|
92
|
+
if (!fmt)
|
|
93
|
+
break;
|
|
94
|
+
const text = decodeXmlText(fmt.inner.trim());
|
|
95
|
+
if (text.length > 0)
|
|
96
|
+
formats.push(text);
|
|
97
|
+
cursor = fmt.endIndex;
|
|
98
|
+
}
|
|
99
|
+
return formats;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Walk every `<Layer>` element directly inside `parentInner`, applying
|
|
103
|
+
* inherited CRS / dimension / style fields from ancestors per WMS 1.3
|
|
104
|
+
* §7.2.4.6 inheritance rules. Iterative (no recursion through closures)
|
|
105
|
+
* to keep the parse hot path small.
|
|
106
|
+
*/
|
|
107
|
+
function* iterChildLayers(parentInner, ancestors) {
|
|
108
|
+
let cursor = 0;
|
|
109
|
+
while (cursor < parentInner.length) {
|
|
110
|
+
const layer = findElement(parentInner, cursor, "Layer");
|
|
111
|
+
if (!layer)
|
|
112
|
+
break;
|
|
113
|
+
const queryableAttr = readAttribute(layer.openTag, "queryable");
|
|
114
|
+
const inherited = mergeAncestors(ancestors);
|
|
115
|
+
// Strip nested <Layer>...</Layer> subtrees from the layer's own inner so
|
|
116
|
+
// descendant Name / Title / CRS / BoundingBox / Style / Dimension nodes
|
|
117
|
+
// do not leak upward into the parent (and from there into siblings via
|
|
118
|
+
// `mergeAncestors`). WMS 1.3 §7.2.4.6 inheritance is ancestor → descendant
|
|
119
|
+
// only; without this guard, an unnamed group layer takes its first child's
|
|
120
|
+
// name and bleeds the child's style across siblings.
|
|
121
|
+
const ownInner = stripNestedLayers(layer.inner);
|
|
122
|
+
const name = readTextElement(ownInner, "Name") ?? "";
|
|
123
|
+
const title = readTextElement(ownInner, "Title");
|
|
124
|
+
const abstract = readTextElement(ownInner, "Abstract");
|
|
125
|
+
const localCrs = collectChildText(ownInner, "CRS");
|
|
126
|
+
const localBbox = collectBoundingBoxes(ownInner);
|
|
127
|
+
const localStyles = collectStyles(ownInner);
|
|
128
|
+
const localDimensions = collectDimensions(ownInner);
|
|
129
|
+
const mergedCrs = uniqueOrder([...inherited.crs, ...localCrs]);
|
|
130
|
+
const mergedBbox = mergeBoundingBoxes(inherited.bbox, localBbox);
|
|
131
|
+
const mergedStyles = mergeStyles(inherited.styles, localStyles);
|
|
132
|
+
const mergedDimensions = mergeDimensions(inherited.dimensions, localDimensions);
|
|
133
|
+
const queryable = queryableAttr === "1" || /^true$/i.test(queryableAttr ?? "")
|
|
134
|
+
? true
|
|
135
|
+
: queryableAttr === "0"
|
|
136
|
+
? false
|
|
137
|
+
: inherited.queryable;
|
|
138
|
+
const node = {
|
|
139
|
+
name,
|
|
140
|
+
...(title !== undefined ? { title } : {}),
|
|
141
|
+
...(abstract !== undefined ? { abstract } : {}),
|
|
142
|
+
crs: mergedCrs,
|
|
143
|
+
bbox: mergedBbox,
|
|
144
|
+
styles: mergedStyles,
|
|
145
|
+
dimensions: mergedDimensions,
|
|
146
|
+
queryable,
|
|
147
|
+
children: [],
|
|
148
|
+
};
|
|
149
|
+
const ancestorsForChildren = [...ancestors, node];
|
|
150
|
+
const children = [];
|
|
151
|
+
for (const child of iterChildLayers(layer.inner, ancestorsForChildren)) {
|
|
152
|
+
children.push(child);
|
|
153
|
+
}
|
|
154
|
+
yield { ...node, children };
|
|
155
|
+
cursor = layer.endIndex;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
function mergeAncestors(ancestors) {
|
|
159
|
+
if (ancestors.length === 0) {
|
|
160
|
+
return { crs: [], bbox: [], styles: [], dimensions: [], queryable: false };
|
|
161
|
+
}
|
|
162
|
+
let crs = [];
|
|
163
|
+
let bbox = [];
|
|
164
|
+
let styles = [];
|
|
165
|
+
let dimensions = [];
|
|
166
|
+
let queryable = false;
|
|
167
|
+
for (const ancestor of ancestors) {
|
|
168
|
+
crs = uniqueOrder([...crs, ...ancestor.crs]);
|
|
169
|
+
bbox = mergeBoundingBoxes(bbox, ancestor.bbox);
|
|
170
|
+
styles = mergeStyles(styles, ancestor.styles);
|
|
171
|
+
dimensions = mergeDimensions(dimensions, ancestor.dimensions);
|
|
172
|
+
queryable = queryable || ancestor.queryable;
|
|
173
|
+
}
|
|
174
|
+
return { crs, bbox, styles, dimensions, queryable };
|
|
175
|
+
}
|
|
176
|
+
function uniqueOrder(values) {
|
|
177
|
+
const seen = new Set();
|
|
178
|
+
const out = [];
|
|
179
|
+
for (const v of values) {
|
|
180
|
+
if (seen.has(v))
|
|
181
|
+
continue;
|
|
182
|
+
seen.add(v);
|
|
183
|
+
out.push(v);
|
|
184
|
+
}
|
|
185
|
+
return out;
|
|
186
|
+
}
|
|
187
|
+
function mergeBoundingBoxes(parent, local) {
|
|
188
|
+
if (local.length === 0)
|
|
189
|
+
return parent;
|
|
190
|
+
const out = [];
|
|
191
|
+
const seen = new Set();
|
|
192
|
+
for (const bb of local) {
|
|
193
|
+
seen.add(bb.crs);
|
|
194
|
+
out.push(bb);
|
|
195
|
+
}
|
|
196
|
+
for (const bb of parent) {
|
|
197
|
+
if (!seen.has(bb.crs))
|
|
198
|
+
out.push(bb);
|
|
199
|
+
}
|
|
200
|
+
return out;
|
|
201
|
+
}
|
|
202
|
+
function mergeStyles(parent, local) {
|
|
203
|
+
if (parent.length === 0)
|
|
204
|
+
return local;
|
|
205
|
+
const seen = new Set(local.map((s) => s.name));
|
|
206
|
+
const out = [...local];
|
|
207
|
+
for (const s of parent) {
|
|
208
|
+
if (!seen.has(s.name))
|
|
209
|
+
out.push(s);
|
|
210
|
+
}
|
|
211
|
+
return out;
|
|
212
|
+
}
|
|
213
|
+
function mergeDimensions(parent, local) {
|
|
214
|
+
if (parent.length === 0)
|
|
215
|
+
return local;
|
|
216
|
+
const seen = new Set(local.map((d) => d.name));
|
|
217
|
+
const out = [...local];
|
|
218
|
+
for (const d of parent) {
|
|
219
|
+
if (!seen.has(d.name))
|
|
220
|
+
out.push(d);
|
|
221
|
+
}
|
|
222
|
+
return out;
|
|
223
|
+
}
|
|
224
|
+
function collectBoundingBoxes(inner) {
|
|
225
|
+
const out = [];
|
|
226
|
+
let cursor = 0;
|
|
227
|
+
while (cursor < inner.length) {
|
|
228
|
+
const node = findElement(inner, cursor, "BoundingBox");
|
|
229
|
+
if (!node)
|
|
230
|
+
break;
|
|
231
|
+
const crs = readAttribute(node.openTag, "CRS") ?? readAttribute(node.openTag, "SRS");
|
|
232
|
+
const minx = parseFloatAttr(node.openTag, "minx");
|
|
233
|
+
const miny = parseFloatAttr(node.openTag, "miny");
|
|
234
|
+
const maxx = parseFloatAttr(node.openTag, "maxx");
|
|
235
|
+
const maxy = parseFloatAttr(node.openTag, "maxy");
|
|
236
|
+
if (crs && Number.isFinite(minx) && Number.isFinite(miny) && Number.isFinite(maxx) && Number.isFinite(maxy)) {
|
|
237
|
+
out.push({ crs, minx, miny, maxx, maxy });
|
|
238
|
+
}
|
|
239
|
+
cursor = node.endIndex;
|
|
240
|
+
}
|
|
241
|
+
// EX_GeographicBoundingBox is the WMS 1.3 alternative geographic bbox node
|
|
242
|
+
// (always WGS84). honua-server emits both; we pull it as a CRS:84 entry
|
|
243
|
+
// when the layer has not advertised one already.
|
|
244
|
+
const geo = findElement(inner, 0, "EX_GeographicBoundingBox");
|
|
245
|
+
if (geo) {
|
|
246
|
+
const w = parseFloatText(readTextElement(geo.inner, "westBoundLongitude"));
|
|
247
|
+
const e = parseFloatText(readTextElement(geo.inner, "eastBoundLongitude"));
|
|
248
|
+
const s = parseFloatText(readTextElement(geo.inner, "southBoundLatitude"));
|
|
249
|
+
const n = parseFloatText(readTextElement(geo.inner, "northBoundLatitude"));
|
|
250
|
+
if (Number.isFinite(w) &&
|
|
251
|
+
Number.isFinite(e) &&
|
|
252
|
+
Number.isFinite(s) &&
|
|
253
|
+
Number.isFinite(n) &&
|
|
254
|
+
!out.some((bb) => bb.crs === "CRS:84")) {
|
|
255
|
+
out.push({ crs: "CRS:84", minx: w, miny: s, maxx: e, maxy: n });
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
return out;
|
|
259
|
+
}
|
|
260
|
+
function collectStyles(inner) {
|
|
261
|
+
const out = [];
|
|
262
|
+
let cursor = 0;
|
|
263
|
+
while (cursor < inner.length) {
|
|
264
|
+
const node = findElement(inner, cursor, "Style");
|
|
265
|
+
if (!node)
|
|
266
|
+
break;
|
|
267
|
+
const name = readTextElement(node.inner, "Name") ?? "";
|
|
268
|
+
const title = readTextElement(node.inner, "Title");
|
|
269
|
+
const legend = findElement(node.inner, 0, "LegendURL");
|
|
270
|
+
const onlineResource = legend ? findElement(legend.inner, 0, "OnlineResource") : undefined;
|
|
271
|
+
const legendUrl = onlineResource ? readAttribute(onlineResource.openTag, "xlink:href") : undefined;
|
|
272
|
+
if (name.length > 0) {
|
|
273
|
+
const entry = { name };
|
|
274
|
+
if (title !== undefined)
|
|
275
|
+
entry.title = title;
|
|
276
|
+
if (legendUrl !== undefined)
|
|
277
|
+
entry.legendUrl = legendUrl;
|
|
278
|
+
out.push(entry);
|
|
279
|
+
}
|
|
280
|
+
cursor = node.endIndex;
|
|
281
|
+
}
|
|
282
|
+
return out;
|
|
283
|
+
}
|
|
284
|
+
function collectDimensions(inner) {
|
|
285
|
+
const out = [];
|
|
286
|
+
let cursor = 0;
|
|
287
|
+
while (cursor < inner.length) {
|
|
288
|
+
const node = findElement(inner, cursor, "Dimension");
|
|
289
|
+
if (!node)
|
|
290
|
+
break;
|
|
291
|
+
const name = readAttribute(node.openTag, "name");
|
|
292
|
+
if (typeof name === "string" && name.length > 0) {
|
|
293
|
+
const units = readAttribute(node.openTag, "units");
|
|
294
|
+
const def = readAttribute(node.openTag, "default");
|
|
295
|
+
const text = decodeXmlText(node.inner.trim());
|
|
296
|
+
const values = text.length > 0
|
|
297
|
+
? text
|
|
298
|
+
.split(",")
|
|
299
|
+
.map((v) => v.trim())
|
|
300
|
+
.filter((v) => v.length > 0)
|
|
301
|
+
: [];
|
|
302
|
+
const dim = { name, values };
|
|
303
|
+
if (units !== undefined)
|
|
304
|
+
dim.units = units;
|
|
305
|
+
if (def !== undefined)
|
|
306
|
+
dim.default = def;
|
|
307
|
+
out.push(dim);
|
|
308
|
+
}
|
|
309
|
+
cursor = node.endIndex;
|
|
310
|
+
}
|
|
311
|
+
return out;
|
|
312
|
+
}
|
|
313
|
+
function collectChildText(inner, tag) {
|
|
314
|
+
const out = [];
|
|
315
|
+
let cursor = 0;
|
|
316
|
+
while (cursor < inner.length) {
|
|
317
|
+
const node = findElement(inner, cursor, tag);
|
|
318
|
+
if (!node)
|
|
319
|
+
break;
|
|
320
|
+
const text = decodeXmlText(node.inner.trim());
|
|
321
|
+
if (text.length > 0)
|
|
322
|
+
out.push(text);
|
|
323
|
+
cursor = node.endIndex;
|
|
324
|
+
}
|
|
325
|
+
return out;
|
|
326
|
+
}
|
|
327
|
+
/**
|
|
328
|
+
* Return `inner` with every nested `<Layer>...</Layer>` (and self-closed
|
|
329
|
+
* `<Layer .../>`) subtree elided. Used by `iterChildLayers` to read the
|
|
330
|
+
* layer's *own* metadata without walking into descendant layers.
|
|
331
|
+
*/
|
|
332
|
+
function stripNestedLayers(inner) {
|
|
333
|
+
if (inner.indexOf("<Layer") < 0)
|
|
334
|
+
return inner;
|
|
335
|
+
let cursor = 0;
|
|
336
|
+
let out = "";
|
|
337
|
+
while (cursor < inner.length) {
|
|
338
|
+
const node = findElement(inner, cursor, "Layer");
|
|
339
|
+
if (!node) {
|
|
340
|
+
out += inner.slice(cursor);
|
|
341
|
+
break;
|
|
342
|
+
}
|
|
343
|
+
out += inner.slice(cursor, node.startIndex);
|
|
344
|
+
cursor = node.endIndex;
|
|
345
|
+
}
|
|
346
|
+
return out;
|
|
347
|
+
}
|
|
348
|
+
/**
|
|
349
|
+
* Find the next `<tag …>…</tag>` (or `<tag …/>`) starting at `from`. The
|
|
350
|
+
* search ignores element nesting other than the requested tag and walks
|
|
351
|
+
* a balanced depth counter for the same tag so nested same-name elements
|
|
352
|
+
* (`<Layer>` inside `<Layer>`) terminate against the correct close. Skips
|
|
353
|
+
* XML comments (`<!-- … -->`) and CDATA sections (`<![CDATA[ … ]]>`).
|
|
354
|
+
*/
|
|
355
|
+
function findElement(xml, from, tag) {
|
|
356
|
+
let i = from;
|
|
357
|
+
while (i < xml.length) {
|
|
358
|
+
if (xml[i] !== "<") {
|
|
359
|
+
i += 1;
|
|
360
|
+
continue;
|
|
361
|
+
}
|
|
362
|
+
const next = xml[i + 1];
|
|
363
|
+
if (next === "!" && xml.startsWith("<!--", i)) {
|
|
364
|
+
const end = xml.indexOf("-->", i + 4);
|
|
365
|
+
if (end < 0)
|
|
366
|
+
return undefined;
|
|
367
|
+
i = end + 3;
|
|
368
|
+
continue;
|
|
369
|
+
}
|
|
370
|
+
if (next === "!" && xml.startsWith("<![CDATA[", i)) {
|
|
371
|
+
const end = xml.indexOf("]]>", i + 9);
|
|
372
|
+
if (end < 0)
|
|
373
|
+
return undefined;
|
|
374
|
+
i = end + 3;
|
|
375
|
+
continue;
|
|
376
|
+
}
|
|
377
|
+
if (next === "?" || next === "!") {
|
|
378
|
+
const end = xml.indexOf(">", i);
|
|
379
|
+
if (end < 0)
|
|
380
|
+
return undefined;
|
|
381
|
+
i = end + 1;
|
|
382
|
+
continue;
|
|
383
|
+
}
|
|
384
|
+
const isClosing = next === "/";
|
|
385
|
+
const nameStart = isClosing ? i + 2 : i + 1;
|
|
386
|
+
const matchesTag = xml.startsWith(tag, nameStart) && isTagBoundary(xml.charCodeAt(nameStart + tag.length));
|
|
387
|
+
if (!matchesTag) {
|
|
388
|
+
const close = xml.indexOf(">", i);
|
|
389
|
+
if (close < 0)
|
|
390
|
+
return undefined;
|
|
391
|
+
i = close + 1;
|
|
392
|
+
continue;
|
|
393
|
+
}
|
|
394
|
+
const close = xml.indexOf(">", i);
|
|
395
|
+
if (close < 0)
|
|
396
|
+
return undefined;
|
|
397
|
+
const openTag = xml.slice(i, close + 1);
|
|
398
|
+
const selfClosing = !isClosing && xml.charCodeAt(close - 1) === 47; // '/'
|
|
399
|
+
if (selfClosing) {
|
|
400
|
+
return { openTag, inner: "", startIndex: i, endIndex: close + 1 };
|
|
401
|
+
}
|
|
402
|
+
if (isClosing) {
|
|
403
|
+
// unmatched closing tag at this level — caller advanced past the
|
|
404
|
+
// opener. Skip past and keep searching.
|
|
405
|
+
i = close + 1;
|
|
406
|
+
continue;
|
|
407
|
+
}
|
|
408
|
+
// Walk forward looking for the matching close tag, accounting for
|
|
409
|
+
// nested same-name openings.
|
|
410
|
+
let depth = 1;
|
|
411
|
+
let cursor = close + 1;
|
|
412
|
+
while (cursor < xml.length && depth > 0) {
|
|
413
|
+
const angle = xml.indexOf("<", cursor);
|
|
414
|
+
if (angle < 0)
|
|
415
|
+
break;
|
|
416
|
+
const next2 = xml[angle + 1];
|
|
417
|
+
if (next2 === "!" && xml.startsWith("<!--", angle)) {
|
|
418
|
+
const end = xml.indexOf("-->", angle + 4);
|
|
419
|
+
if (end < 0)
|
|
420
|
+
return undefined;
|
|
421
|
+
cursor = end + 3;
|
|
422
|
+
continue;
|
|
423
|
+
}
|
|
424
|
+
if (next2 === "!" && xml.startsWith("<![CDATA[", angle)) {
|
|
425
|
+
const end = xml.indexOf("]]>", angle + 9);
|
|
426
|
+
if (end < 0)
|
|
427
|
+
return undefined;
|
|
428
|
+
cursor = end + 3;
|
|
429
|
+
continue;
|
|
430
|
+
}
|
|
431
|
+
const isCloser = next2 === "/";
|
|
432
|
+
const inspectStart = isCloser ? angle + 2 : angle + 1;
|
|
433
|
+
const matchesSame = xml.startsWith(tag, inspectStart) && isTagBoundary(xml.charCodeAt(inspectStart + tag.length));
|
|
434
|
+
const angleClose = xml.indexOf(">", angle);
|
|
435
|
+
if (angleClose < 0)
|
|
436
|
+
return undefined;
|
|
437
|
+
if (matchesSame) {
|
|
438
|
+
if (isCloser) {
|
|
439
|
+
depth -= 1;
|
|
440
|
+
if (depth === 0) {
|
|
441
|
+
return {
|
|
442
|
+
openTag,
|
|
443
|
+
inner: xml.slice(close + 1, angle),
|
|
444
|
+
startIndex: i,
|
|
445
|
+
endIndex: angleClose + 1,
|
|
446
|
+
};
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
else {
|
|
450
|
+
const innerSelfClose = xml.charCodeAt(angleClose - 1) === 47;
|
|
451
|
+
if (!innerSelfClose)
|
|
452
|
+
depth += 1;
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
cursor = angleClose + 1;
|
|
456
|
+
}
|
|
457
|
+
return undefined;
|
|
458
|
+
}
|
|
459
|
+
return undefined;
|
|
460
|
+
}
|
|
461
|
+
function isTagBoundary(code) {
|
|
462
|
+
// Whitespace, '/', or '>' terminate a tag name.
|
|
463
|
+
return code === 32 || code === 9 || code === 10 || code === 13 || code === 47 || code === 62;
|
|
464
|
+
}
|
|
465
|
+
function readAttribute(openTag, name) {
|
|
466
|
+
// Match `name="value"` or `name='value'`. Tolerates leading whitespace
|
|
467
|
+
// and case-sensitive name match (WMS attribute names are case-sensitive).
|
|
468
|
+
const idx = findAttributeIndex(openTag, name);
|
|
469
|
+
if (idx < 0)
|
|
470
|
+
return undefined;
|
|
471
|
+
const after = openTag.slice(idx + name.length);
|
|
472
|
+
const eq = after.indexOf("=");
|
|
473
|
+
if (eq < 0)
|
|
474
|
+
return undefined;
|
|
475
|
+
let cursor = eq + 1;
|
|
476
|
+
while (cursor < after.length && /\s/.test(after[cursor]))
|
|
477
|
+
cursor += 1;
|
|
478
|
+
const quote = after[cursor];
|
|
479
|
+
if (quote !== '"' && quote !== "'")
|
|
480
|
+
return undefined;
|
|
481
|
+
const end = after.indexOf(quote, cursor + 1);
|
|
482
|
+
if (end < 0)
|
|
483
|
+
return undefined;
|
|
484
|
+
return decodeXmlText(after.slice(cursor + 1, end));
|
|
485
|
+
}
|
|
486
|
+
function findAttributeIndex(openTag, name) {
|
|
487
|
+
let i = 0;
|
|
488
|
+
while (i < openTag.length) {
|
|
489
|
+
const found = openTag.indexOf(name, i);
|
|
490
|
+
if (found < 0)
|
|
491
|
+
return -1;
|
|
492
|
+
const before = openTag.charCodeAt(found - 1);
|
|
493
|
+
const isBoundaryStart = found === 0 || before === 32 || before === 9 || before === 10 || before === 13;
|
|
494
|
+
const after = openTag.charCodeAt(found + name.length);
|
|
495
|
+
const isBoundaryEnd = after === 61 || after === 32 || after === 9 || after === 10 || after === 13;
|
|
496
|
+
if (isBoundaryStart && isBoundaryEnd) {
|
|
497
|
+
return found;
|
|
498
|
+
}
|
|
499
|
+
i = found + 1;
|
|
500
|
+
}
|
|
501
|
+
return -1;
|
|
502
|
+
}
|
|
503
|
+
function readTextElement(xml, tag) {
|
|
504
|
+
const node = findElement(xml, 0, tag);
|
|
505
|
+
if (!node)
|
|
506
|
+
return undefined;
|
|
507
|
+
const decoded = decodeXmlText(node.inner.trim());
|
|
508
|
+
return decoded;
|
|
509
|
+
}
|
|
510
|
+
function parseFloatAttr(openTag, name) {
|
|
511
|
+
const value = readAttribute(openTag, name);
|
|
512
|
+
if (value === undefined)
|
|
513
|
+
return Number.NaN;
|
|
514
|
+
const parsed = Number(value);
|
|
515
|
+
return Number.isFinite(parsed) ? parsed : Number.NaN;
|
|
516
|
+
}
|
|
517
|
+
function parseFloatText(value) {
|
|
518
|
+
if (value === undefined)
|
|
519
|
+
return Number.NaN;
|
|
520
|
+
const parsed = Number(value);
|
|
521
|
+
return Number.isFinite(parsed) ? parsed : Number.NaN;
|
|
522
|
+
}
|
|
523
|
+
function decodeXmlText(text) {
|
|
524
|
+
if (text.indexOf("&") < 0 && text.indexOf("<![CDATA[") < 0)
|
|
525
|
+
return text;
|
|
526
|
+
return text
|
|
527
|
+
.replace(/<!\[CDATA\[([\s\S]*?)\]\]>/g, "$1")
|
|
528
|
+
.replace(/</g, "<")
|
|
529
|
+
.replace(/>/g, ">")
|
|
530
|
+
.replace(/&/g, "&")
|
|
531
|
+
.replace(/'/g, "'")
|
|
532
|
+
.replace(/"/g, '"')
|
|
533
|
+
.replace(/&#(\d+);/g, (_match, dec) => String.fromCodePoint(Number(dec)))
|
|
534
|
+
.replace(/&#x([0-9a-fA-F]+);/g, (_match, hex) => String.fromCodePoint(Number.parseInt(hex, 16)));
|
|
535
|
+
}
|
|
536
|
+
/**
|
|
537
|
+
* Walk a parsed `WmsCapabilities` tree and yield every named layer
|
|
538
|
+
* (skipping container layers without a `name`). Useful for descriptor
|
|
539
|
+
* resolution: callers want to bind a `LAYER` name without re-walking
|
|
540
|
+
* the parent hierarchy.
|
|
541
|
+
*/
|
|
542
|
+
export function* iterateWmsLayers(capabilities) {
|
|
543
|
+
const stack = [...capabilities.layers];
|
|
544
|
+
while (stack.length > 0) {
|
|
545
|
+
const next = stack.shift();
|
|
546
|
+
if (!next)
|
|
547
|
+
break;
|
|
548
|
+
if (next.name.length > 0)
|
|
549
|
+
yield next;
|
|
550
|
+
for (const child of next.children) {
|
|
551
|
+
stack.push(child);
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
/**
|
|
556
|
+
* Find the first named layer with a matching name, or `undefined` if the
|
|
557
|
+
* capabilities document does not advertise it. Walks both root layers and
|
|
558
|
+
* children. Intended for the `HonuaWms.layer(name)` resolver path.
|
|
559
|
+
*/
|
|
560
|
+
export function findWmsLayer(capabilities, name) {
|
|
561
|
+
for (const layer of iterateWmsLayers(capabilities)) {
|
|
562
|
+
if (layer.name === name)
|
|
563
|
+
return layer;
|
|
564
|
+
}
|
|
565
|
+
return undefined;
|
|
566
|
+
}
|