@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.
Files changed (289) hide show
  1. package/README.md +5 -0
  2. package/contract/edit-session.d.ts +203 -0
  3. package/contract/edit-session.d.ts.map +1 -0
  4. package/contract/edit-session.js +717 -0
  5. package/contract/index.d.ts +20 -0
  6. package/contract/index.d.ts.map +1 -0
  7. package/contract/index.js +15 -0
  8. package/contract/jobs.d.ts +115 -0
  9. package/contract/jobs.d.ts.map +1 -0
  10. package/contract/jobs.js +22 -0
  11. package/contract/source.d.ts +138 -0
  12. package/contract/source.d.ts.map +1 -0
  13. package/contract/source.js +3364 -0
  14. package/contract/spatial-aggregation.d.ts +312 -0
  15. package/contract/spatial-aggregation.d.ts.map +1 -0
  16. package/contract/spatial-aggregation.js +196 -0
  17. package/contract/types.d.ts +542 -0
  18. package/contract/types.d.ts.map +1 -0
  19. package/contract/types.js +171 -0
  20. package/core/batch.d.ts +37 -0
  21. package/core/batch.d.ts.map +1 -0
  22. package/core/batch.js +78 -0
  23. package/core/cache-state.d.ts +76 -0
  24. package/core/cache-state.d.ts.map +1 -0
  25. package/core/cache-state.js +84 -0
  26. package/core/client.d.ts +289 -0
  27. package/core/client.d.ts.map +1 -0
  28. package/core/client.js +2625 -0
  29. package/core/errors.d.ts +68 -0
  30. package/core/errors.d.ts.map +1 -0
  31. package/core/errors.js +116 -0
  32. package/core/grpc-adapter.d.ts +23 -0
  33. package/core/grpc-adapter.d.ts.map +1 -0
  34. package/core/grpc-adapter.js +704 -0
  35. package/core/odata.d.ts +347 -0
  36. package/core/odata.d.ts.map +1 -0
  37. package/core/odata.js +837 -0
  38. package/core/ogc-conformance.d.ts +35 -0
  39. package/core/ogc-conformance.d.ts.map +1 -0
  40. package/core/ogc-conformance.js +121 -0
  41. package/core/ogc-maps.d.ts +45 -0
  42. package/core/ogc-maps.d.ts.map +1 -0
  43. package/core/ogc-maps.js +53 -0
  44. package/core/ogc-processes.d.ts +92 -0
  45. package/core/ogc-processes.d.ts.map +1 -0
  46. package/core/ogc-processes.js +325 -0
  47. package/core/ogc-tiles.d.ts +55 -0
  48. package/core/ogc-tiles.d.ts.map +1 -0
  49. package/core/ogc-tiles.js +81 -0
  50. package/core/pbf-decoder.d.ts +28 -0
  51. package/core/pbf-decoder.d.ts.map +1 -0
  52. package/core/pbf-decoder.js +627 -0
  53. package/core/query-builder.d.ts +261 -0
  54. package/core/query-builder.d.ts.map +1 -0
  55. package/core/query-builder.js +538 -0
  56. package/core/spatial-filter.d.ts +93 -0
  57. package/core/spatial-filter.d.ts.map +1 -0
  58. package/core/spatial-filter.js +153 -0
  59. package/core/stac.d.ts +43 -0
  60. package/core/stac.d.ts.map +1 -0
  61. package/core/stac.js +116 -0
  62. package/core/surfaces.d.ts +464 -0
  63. package/core/surfaces.d.ts.map +1 -0
  64. package/core/surfaces.js +1322 -0
  65. package/core/types.d.ts +1238 -0
  66. package/core/types.d.ts.map +1 -0
  67. package/core/types.js +13 -0
  68. package/core/wfs-capabilities.d.ts +143 -0
  69. package/core/wfs-capabilities.d.ts.map +1 -0
  70. package/core/wfs-capabilities.js +478 -0
  71. package/core/wfs-filter.d.ts +118 -0
  72. package/core/wfs-filter.d.ts.map +1 -0
  73. package/core/wfs-filter.js +586 -0
  74. package/core/wfs.d.ts +219 -0
  75. package/core/wfs.d.ts.map +1 -0
  76. package/core/wfs.js +406 -0
  77. package/core/wms-capabilities.d.ts +110 -0
  78. package/core/wms-capabilities.d.ts.map +1 -0
  79. package/core/wms-capabilities.js +566 -0
  80. package/core/wms-types.d.ts +128 -0
  81. package/core/wms-types.d.ts.map +1 -0
  82. package/core/wms-types.js +32 -0
  83. package/core/wms.d.ts +108 -0
  84. package/core/wms.d.ts.map +1 -0
  85. package/core/wms.js +182 -0
  86. package/core/wmts-capabilities.d.ts +82 -0
  87. package/core/wmts-capabilities.d.ts.map +1 -0
  88. package/core/wmts-capabilities.js +409 -0
  89. package/core/wmts.d.ts +80 -0
  90. package/core/wmts.d.ts.map +1 -0
  91. package/core/wmts.js +173 -0
  92. package/esri-compat/basemap-gallery.d.ts +38 -0
  93. package/esri-compat/basemap-gallery.d.ts.map +1 -0
  94. package/esri-compat/basemap-gallery.js +159 -0
  95. package/esri-compat/basemap-layer-list.d.ts +35 -0
  96. package/esri-compat/basemap-layer-list.d.ts.map +1 -0
  97. package/esri-compat/basemap-layer-list.js +139 -0
  98. package/esri-compat/basemap.d.ts +32 -0
  99. package/esri-compat/basemap.d.ts.map +1 -0
  100. package/esri-compat/basemap.js +84 -0
  101. package/esri-compat/bookmarks.d.ts +36 -0
  102. package/esri-compat/bookmarks.d.ts.map +1 -0
  103. package/esri-compat/bookmarks.js +108 -0
  104. package/esri-compat/class-breaks-renderer.d.ts +50 -0
  105. package/esri-compat/class-breaks-renderer.d.ts.map +1 -0
  106. package/esri-compat/class-breaks-renderer.js +164 -0
  107. package/esri-compat/color.d.ts +22 -0
  108. package/esri-compat/color.d.ts.map +1 -0
  109. package/esri-compat/color.js +106 -0
  110. package/esri-compat/controls.d.ts +174 -0
  111. package/esri-compat/controls.d.ts.map +1 -0
  112. package/esri-compat/controls.js +479 -0
  113. package/esri-compat/coordinate-conversion.d.ts +41 -0
  114. package/esri-compat/coordinate-conversion.d.ts.map +1 -0
  115. package/esri-compat/coordinate-conversion.js +125 -0
  116. package/esri-compat/directions.d.ts +45 -0
  117. package/esri-compat/directions.d.ts.map +1 -0
  118. package/esri-compat/directions.js +123 -0
  119. package/esri-compat/editor.d.ts +47 -0
  120. package/esri-compat/editor.d.ts.map +1 -0
  121. package/esri-compat/editor.js +170 -0
  122. package/esri-compat/esri-config.d.ts +16 -0
  123. package/esri-compat/esri-config.d.ts.map +1 -0
  124. package/esri-compat/esri-config.js +23 -0
  125. package/esri-compat/esri-request.d.ts +17 -0
  126. package/esri-compat/esri-request.d.ts.map +1 -0
  127. package/esri-compat/esri-request.js +66 -0
  128. package/esri-compat/event-bus.d.ts +106 -0
  129. package/esri-compat/event-bus.d.ts.map +1 -0
  130. package/esri-compat/event-bus.js +90 -0
  131. package/esri-compat/expand.d.ts +36 -0
  132. package/esri-compat/expand.d.ts.map +1 -0
  133. package/esri-compat/expand.js +97 -0
  134. package/esri-compat/extent.d.ts +43 -0
  135. package/esri-compat/extent.d.ts.map +1 -0
  136. package/esri-compat/extent.js +140 -0
  137. package/esri-compat/feature-form.d.ts +64 -0
  138. package/esri-compat/feature-form.d.ts.map +1 -0
  139. package/esri-compat/feature-form.js +137 -0
  140. package/esri-compat/feature-layer.d.ts +173 -0
  141. package/esri-compat/feature-layer.d.ts.map +1 -0
  142. package/esri-compat/feature-layer.js +655 -0
  143. package/esri-compat/feature-set.d.ts +32 -0
  144. package/esri-compat/feature-set.d.ts.map +1 -0
  145. package/esri-compat/feature-set.js +80 -0
  146. package/esri-compat/feature-table.d.ts +141 -0
  147. package/esri-compat/feature-table.d.ts.map +1 -0
  148. package/esri-compat/feature-table.js +500 -0
  149. package/esri-compat/feature-templates.d.ts +41 -0
  150. package/esri-compat/feature-templates.d.ts.map +1 -0
  151. package/esri-compat/feature-templates.js +97 -0
  152. package/esri-compat/feature.d.ts +33 -0
  153. package/esri-compat/feature.d.ts.map +1 -0
  154. package/esri-compat/feature.js +83 -0
  155. package/esri-compat/graphic.d.ts +46 -0
  156. package/esri-compat/graphic.d.ts.map +1 -0
  157. package/esri-compat/graphic.js +103 -0
  158. package/esri-compat/graphics-layer.d.ts +49 -0
  159. package/esri-compat/graphics-layer.d.ts.map +1 -0
  160. package/esri-compat/graphics-layer.js +185 -0
  161. package/esri-compat/group-layer.d.ts +48 -0
  162. package/esri-compat/group-layer.d.ts.map +1 -0
  163. package/esri-compat/group-layer.js +212 -0
  164. package/esri-compat/identify.d.ts +71 -0
  165. package/esri-compat/identify.d.ts.map +1 -0
  166. package/esri-compat/identify.js +299 -0
  167. package/esri-compat/identity-manager.d.ts +31 -0
  168. package/esri-compat/identity-manager.d.ts.map +1 -0
  169. package/esri-compat/identity-manager.js +115 -0
  170. package/esri-compat/label-class.d.ts +31 -0
  171. package/esri-compat/label-class.d.ts.map +1 -0
  172. package/esri-compat/label-class.js +102 -0
  173. package/esri-compat/layer-list.d.ts +77 -0
  174. package/esri-compat/layer-list.d.ts.map +1 -0
  175. package/esri-compat/layer-list.js +342 -0
  176. package/esri-compat/legend.d.ts +53 -0
  177. package/esri-compat/legend.d.ts.map +1 -0
  178. package/esri-compat/legend.js +254 -0
  179. package/esri-compat/map-image-layer.d.ts +162 -0
  180. package/esri-compat/map-image-layer.d.ts.map +1 -0
  181. package/esri-compat/map-image-layer.js +704 -0
  182. package/esri-compat/map-view.d.ts +315 -0
  183. package/esri-compat/map-view.d.ts.map +1 -0
  184. package/esri-compat/map-view.js +1110 -0
  185. package/esri-compat/map.d.ts +47 -0
  186. package/esri-compat/map.d.ts.map +1 -0
  187. package/esri-compat/map.js +209 -0
  188. package/esri-compat/measurement-2d.d.ts +61 -0
  189. package/esri-compat/measurement-2d.d.ts.map +1 -0
  190. package/esri-compat/measurement-2d.js +184 -0
  191. package/esri-compat/measurement.d.ts +45 -0
  192. package/esri-compat/measurement.d.ts.map +1 -0
  193. package/esri-compat/measurement.js +193 -0
  194. package/esri-compat/oauth-info.d.ts +35 -0
  195. package/esri-compat/oauth-info.d.ts.map +1 -0
  196. package/esri-compat/oauth-info.js +115 -0
  197. package/esri-compat/picture-marker-symbol.d.ts +35 -0
  198. package/esri-compat/picture-marker-symbol.d.ts.map +1 -0
  199. package/esri-compat/picture-marker-symbol.js +126 -0
  200. package/esri-compat/point.d.ts +31 -0
  201. package/esri-compat/point.d.ts.map +1 -0
  202. package/esri-compat/point.js +102 -0
  203. package/esri-compat/polygon.d.ts +31 -0
  204. package/esri-compat/polygon.d.ts.map +1 -0
  205. package/esri-compat/polygon.js +107 -0
  206. package/esri-compat/polyline.d.ts +31 -0
  207. package/esri-compat/polyline.d.ts.map +1 -0
  208. package/esri-compat/polyline.js +107 -0
  209. package/esri-compat/popup-template.d.ts +55 -0
  210. package/esri-compat/popup-template.d.ts.map +1 -0
  211. package/esri-compat/popup-template.js +128 -0
  212. package/esri-compat/popup.d.ts +54 -0
  213. package/esri-compat/popup.d.ts.map +1 -0
  214. package/esri-compat/popup.js +312 -0
  215. package/esri-compat/print.d.ts +47 -0
  216. package/esri-compat/print.d.ts.map +1 -0
  217. package/esri-compat/print.js +105 -0
  218. package/esri-compat/query.d.ts +46 -0
  219. package/esri-compat/query.d.ts.map +1 -0
  220. package/esri-compat/query.js +119 -0
  221. package/esri-compat/reactive-utils.d.ts +23 -0
  222. package/esri-compat/reactive-utils.d.ts.map +1 -0
  223. package/esri-compat/reactive-utils.js +178 -0
  224. package/esri-compat/request.d.ts +41 -0
  225. package/esri-compat/request.d.ts.map +1 -0
  226. package/esri-compat/request.js +253 -0
  227. package/esri-compat/route-layer.d.ts +59 -0
  228. package/esri-compat/route-layer.d.ts.map +1 -0
  229. package/esri-compat/route-layer.js +223 -0
  230. package/esri-compat/route-task.d.ts +88 -0
  231. package/esri-compat/route-task.d.ts.map +1 -0
  232. package/esri-compat/route-task.js +225 -0
  233. package/esri-compat/scene-view.d.ts +41 -0
  234. package/esri-compat/scene-view.d.ts.map +1 -0
  235. package/esri-compat/scene-view.js +68 -0
  236. package/esri-compat/search.d.ts +112 -0
  237. package/esri-compat/search.d.ts.map +1 -0
  238. package/esri-compat/search.js +820 -0
  239. package/esri-compat/simple-fill-symbol.d.ts +27 -0
  240. package/esri-compat/simple-fill-symbol.d.ts.map +1 -0
  241. package/esri-compat/simple-fill-symbol.js +85 -0
  242. package/esri-compat/simple-line-symbol.d.ts +27 -0
  243. package/esri-compat/simple-line-symbol.d.ts.map +1 -0
  244. package/esri-compat/simple-line-symbol.js +85 -0
  245. package/esri-compat/simple-marker-symbol.d.ts +29 -0
  246. package/esri-compat/simple-marker-symbol.d.ts.map +1 -0
  247. package/esri-compat/simple-marker-symbol.js +92 -0
  248. package/esri-compat/simple-renderer.d.ts +29 -0
  249. package/esri-compat/simple-renderer.d.ts.map +1 -0
  250. package/esri-compat/simple-renderer.js +92 -0
  251. package/esri-compat/sketch.d.ts +64 -0
  252. package/esri-compat/sketch.d.ts.map +1 -0
  253. package/esri-compat/sketch.js +227 -0
  254. package/esri-compat/spatial-reference.d.ts +31 -0
  255. package/esri-compat/spatial-reference.d.ts.map +1 -0
  256. package/esri-compat/spatial-reference.js +102 -0
  257. package/esri-compat/swipe.d.ts +34 -0
  258. package/esri-compat/swipe.d.ts.map +1 -0
  259. package/esri-compat/swipe.js +93 -0
  260. package/esri-compat/table-list.d.ts +36 -0
  261. package/esri-compat/table-list.d.ts.map +1 -0
  262. package/esri-compat/table-list.js +126 -0
  263. package/esri-compat/text-symbol.d.ts +37 -0
  264. package/esri-compat/text-symbol.d.ts.map +1 -0
  265. package/esri-compat/text-symbol.js +123 -0
  266. package/esri-compat/tile-layer.d.ts +52 -0
  267. package/esri-compat/tile-layer.d.ts.map +1 -0
  268. package/esri-compat/tile-layer.js +167 -0
  269. package/esri-compat/time-slider.d.ts +70 -0
  270. package/esri-compat/time-slider.d.ts.map +1 -0
  271. package/esri-compat/time-slider.js +208 -0
  272. package/esri-compat/track.d.ts +51 -0
  273. package/esri-compat/track.d.ts.map +1 -0
  274. package/esri-compat/track.js +174 -0
  275. package/esri-compat/unique-value-renderer.d.ts +41 -0
  276. package/esri-compat/unique-value-renderer.d.ts.map +1 -0
  277. package/esri-compat/unique-value-renderer.js +127 -0
  278. package/esri-compat/url.d.ts +12 -0
  279. package/esri-compat/url.d.ts.map +1 -0
  280. package/esri-compat/url.js +54 -0
  281. package/esri-compat/web-map.d.ts +14 -0
  282. package/esri-compat/web-map.d.ts.map +1 -0
  283. package/esri-compat/web-map.js +24 -0
  284. package/gen/honua/v1/feature_service_pb.d.ts +918 -0
  285. package/gen/honua/v1/feature_service_pb.d.ts.map +1 -0
  286. package/gen/honua/v1/feature_service_pb.js +375 -0
  287. package/index.d.ts +130 -0
  288. package/index.js +65 -0
  289. 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(/&lt;/g, "<")
529
+ .replace(/&gt;/g, ">")
530
+ .replace(/&amp;/g, "&")
531
+ .replace(/&apos;/g, "'")
532
+ .replace(/&quot;/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
+ }