@masterportal/masterportalapi 2.61.0 → 2.63.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/.editorconfig CHANGED
@@ -6,7 +6,7 @@ root = true
6
6
  [*]
7
7
  indent_style = space
8
8
  indent_size = 4
9
- end_of_line = crlf
9
+ end_of_line = lf
10
10
  charset = utf-8
11
11
  trim_trailing_whitespace = true
12
12
  insert_final_newline = true
package/.eslintrc CHANGED
@@ -104,7 +104,6 @@
104
104
  "no-void": "error",
105
105
  "no-with": "error",
106
106
  "radix": "error",
107
- "vars-on-top": "error",
108
107
  "yoda": "error",
109
108
  "no-delete-var": "error",
110
109
  "no-label-var": "error",
@@ -159,7 +158,6 @@
159
158
  "no-whitespace-before-property": "error",
160
159
  "object-curly-spacing": "error",
161
160
  "object-property-newline": 0,
162
- "one-var": "error",
163
161
  "one-var-declaration-per-line": "error",
164
162
  "quotes": "error",
165
163
  "require-jsdoc": 0,
package/CHANGELOG.md CHANGED
@@ -2,6 +2,48 @@
2
2
  All important changes in this project are stored in this file.
3
3
 
4
4
  The [Semantic Versioning](https://semver.org/spec/v2.0.0.html) is used.
5
+
6
+ ## 2.63.0 - 2026-07-17
7
+
8
+ ### Changed
9
+ - The following packages have been updated:
10
+ - dependencies:
11
+ - core-js: 3.33.1 to 3.49.0
12
+ - dayjs: 1.11.10 to 1.11.21
13
+ - proj4: 2.10.0 to 2.20.9
14
+ - devDependencies:
15
+ - @parcel/config-default: 2.15.4 to 2.16.4
16
+ - @parcel/transformer-sass: 2.15.4 to 2.16.4
17
+ - canvas: 3.2.0 to 3.2.3
18
+ - jest: 30.1.3 to 30.4.2
19
+ - jest-environment-jsdom: 30.1.2 to 30.4.1
20
+ - jsdoc: 4.0.2 to 4.0.5
21
+ - parcel: 2.15.4 to 2.16.4
22
+ - sass: 1.69.4 to 1.101.0
23
+ - url: 0.11.3 to 0.11.4
24
+ - @cesium/engine: 23.0.0 to 26.0.0
25
+
26
+ ### Fixed
27
+ - Issue \#1623: fix label for configuration of style properties as array of objects.
28
+
29
+ ---
30
+
31
+ ## 2.62.0 - 2026-06-11
32
+
33
+ ### Added
34
+ - Allow to override the OpenLayers Map class for 2D maps.
35
+
36
+ ### Changed
37
+ - Linting: Removed one-var and vars-on-top rules.
38
+ - Changed end_of_line to lf in editorconfig file.
39
+ - Issue #17: Replaced package xml2js with native DomParser for parsing xml of search result.
40
+
41
+ ### Removed
42
+ - The following packages have been removed:
43
+ - dependencies:
44
+ - xml2js
45
+
46
+ ---
5
47
 
6
48
  ## 2.61.0 - 2026-04-21
7
49
 
@@ -0,0 +1,84 @@
1
+
2
+ # Contributor Covenant Code of Conduct
3
+
4
+ ## Our Pledge
5
+
6
+ We as members, contributors, and leaders pledge to make participation in our
7
+ community a harassment-free experience for everyone, regardless of age, body
8
+ size, visible or invisible disability, ethnicity, sex characteristics, gender
9
+ identity and expression, level of experience, education, socio-economic status,
10
+ nationality, personal appearance, race, caste, color, religion, or sexual
11
+ identity and orientation.
12
+
13
+ We pledge to act and interact in ways that contribute to an open, welcoming,
14
+ diverse, inclusive, and healthy community.
15
+
16
+ ## Our Standards
17
+
18
+ Examples of behavior that contributes to a positive environment for our
19
+ community include:
20
+
21
+ * Demonstrating empathy and kindness toward other people
22
+ * Being respectful of differing opinions, viewpoints, and experiences
23
+ * Giving and gracefully accepting constructive feedback
24
+ * Accepting responsibility and apologizing to those affected by our mistakes,
25
+ and learning from the experience
26
+ * Focusing on what is best not just for us as individuals, but for the overall
27
+ community
28
+
29
+ Examples of unacceptable behavior include:
30
+
31
+ * The use of sexualized language or imagery, and sexual attention or advances of
32
+ any kind
33
+ * Trolling, insulting or derogatory comments, and personal or political attacks
34
+ * Public or private harassment
35
+ * Publishing others' private information, such as a physical or email address,
36
+ without their explicit permission
37
+ * Other conduct which could reasonably be considered inappropriate in a
38
+ professional setting
39
+
40
+ ## Enforcement Responsibilities
41
+
42
+ Community leaders are responsible for clarifying and enforcing our standards of
43
+ acceptable behavior and will take appropriate and fair corrective action in
44
+ response to any behavior that they deem inappropriate, threatening, offensive,
45
+ or harmful.
46
+
47
+ Community leaders have the right and responsibility to remove, edit, or reject
48
+ comments, commits, code, wiki edits, issues, and other contributions that are
49
+ not aligned to this Code of Conduct, and will communicate reasons for moderation
50
+ decisions when appropriate.
51
+
52
+ ## Scope
53
+
54
+ This Code of Conduct applies within all community spaces, and also applies when
55
+ an individual is officially representing the community in public spaces.
56
+ Examples of representing our community include using an official e-mail address,
57
+ posting via an official social media account, or acting as an appointed
58
+ representative at an online or offline event.
59
+
60
+ ## Enforcement
61
+
62
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at geodatenanwendungen@gv.hamburg.de. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
63
+
64
+ Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project’s leadership.
65
+
66
+
67
+ ## Attribution
68
+
69
+ This Code of Conduct is adapted from the [Contributor Covenant][homepage],
70
+ version 2.1, available at
71
+ [https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1].
72
+
73
+ Community Impact Guidelines were inspired by
74
+ [Mozilla's code of conduct enforcement ladder][Mozilla CoC].
75
+
76
+ For answers to common questions about this code of conduct, see the FAQ at
77
+ [https://www.contributor-covenant.org/faq][FAQ]. Translations are available at
78
+ [https://www.contributor-covenant.org/translations][translations].
79
+
80
+ [homepage]: https://www.contributor-covenant.org
81
+ [v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html
82
+ [Mozilla CoC]: https://github.com/mozilla/diversity
83
+ [FAQ]: https://www.contributor-covenant.org/faq
84
+ [translations]: https://www.contributor-covenant.org/translations
@@ -0,0 +1,70 @@
1
+ # Contributing to masterportalAPI
2
+
3
+ Thank you for your interest in contributing to masterportalAPI. This document is the entry point for contributors.
4
+
5
+ If a policy, workflow, or convention is not defined in this repository, the corresponding rules from the [main Masterportal project](https://bitbucket.org/geowerkstatt-hamburg/masterportal/src/dev/) apply.
6
+
7
+ ## Code of Conduct
8
+
9
+ All participants are expected to follow our [Code of Conduct](./CODE_OF_CONDUCT.md).
10
+
11
+ ## Developer Documentation
12
+
13
+ The main developer documentation for this repository is available in these places:
14
+
15
+ - **[Project overview and usage](./README.md)** — Installation, package usage, error handling, and available scripts
16
+ - **[Example setup](./example/README.md)** — How to use the example application for manual testing
17
+ - **[API documentation generation](./README.md#scripts)** — Generate local JSDoc output with `npm run generate-jsdoc`
18
+ - **[Changelog](./CHANGELOG.md)** — Add your changes under `Unreleased`
19
+ - **[Package metadata](./package.json)** — Supported Node.js and npm versions, scripts, dependencies, and repository metadata
20
+
21
+ If you need guidance that is not documented in this repository, follow the corresponding documentation and contribution rules from the [main Masterportal project](https://bitbucket.org/geowerkstatt-hamburg/masterportal/src/dev/).
22
+
23
+ ## Quick Reference
24
+
25
+ | What | Where |
26
+ |---|---|
27
+ | Install dependencies | `npm install` |
28
+ | Start the example dev server | `npm run example` |
29
+ | Run tests | `npm test` |
30
+ | Pre-push check (ESLint + tests) | `npm run prePushHook` |
31
+ | Generate API docs | `npm run generate-jsdoc` |
32
+
33
+ ## Asking Questions
34
+
35
+ If you have questions about using or developing with masterportalAPI, start by checking the [README](./README.md).
36
+
37
+ ## Getting Familiar with the Code in the Repository
38
+
39
+ The repository is organized into a few main areas:
40
+
41
+ - `src/` contains the library source code
42
+ - `test/` contains the automated test suite
43
+ - `example/` contains a Parcel-based example app for manual testing
44
+ - `public/` contains public assets used by the example and runtime utilities
45
+
46
+ If you are new to the codebase, begin with [README.md](./README.md), then inspect the relevant modules in `src/` and the corresponding test files in `test/`.
47
+
48
+ ## How to Contribute
49
+
50
+ 1. **Fork the repository** — external developers do not receive write access. See Forks.
51
+ 2. **Branch from dev** and follow the Git workflow.
52
+ 3. **Write code** that adheres to the coding conventions.
53
+ 4. **Add tests** for new or changed functionality.
54
+ 5. **Update the changelog** — add an entry under `Unreleased` in [CHANGELOG.md](./CHANGELOG.md).
55
+ 6. **Open a Pull Request** against dev. Reviewers will check against the Definition of Done.
56
+
57
+ ## Reporting Issues
58
+
59
+ Use the [Bitbucket Issue Tracker](https://bitbucket.org/geowerkstatt-hamburg/masterportalapi/issues). Please include:
60
+
61
+ - A clear description of the problem or feature request
62
+ - Steps to reproduce (for bugs)
63
+ - Expected vs. actual behavior
64
+ - npm and Node.js version
65
+ - Relevant configuration
66
+ - Screenshots, logs if they help explain the problem
67
+
68
+ ## License
69
+
70
+ By contributing, you agree that your contributions will be licensed under the [MIT License](./License.txt).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@masterportal/masterportalapi",
3
- "version": "2.61.0",
3
+ "version": "2.63.0",
4
4
  "description": "Basic functions of the Masterportal as api",
5
5
  "homepage": "https://bitbucket.org/geowerkstatt-hamburg/masterportalapi#readme",
6
6
  "bugs": {
@@ -33,43 +33,42 @@
33
33
  "prepare": "husky install"
34
34
  },
35
35
  "dependencies": {
36
- "core-js": "^3.33.1",
37
- "dayjs": "^1.11.10",
36
+ "core-js": "^3.49.0",
37
+ "dayjs": "^1.11.21",
38
38
  "ol": "10.9.0",
39
39
  "ol-mapbox-style": "13.3.0",
40
40
  "olcs": "^2.23.0",
41
- "proj4": "^2.10.0",
42
- "xml2js": "^0.6.2"
41
+ "proj4": "^2.20.9"
43
42
  },
44
43
  "devDependencies": {
45
- "@parcel/config-default": "^2.15.4",
46
- "@parcel/transformer-sass": "^2.15.4",
44
+ "@parcel/config-default": "^2.16.4",
45
+ "@parcel/transformer-sass": "^2.16.4",
47
46
  "@swc/jest": "^0.2.39",
48
47
  "assert": "^2.1.0",
49
48
  "browserify-zlib": "^0.2.0",
50
49
  "buffer": "^6.0.3",
51
- "canvas": "3.2.0",
50
+ "canvas": "3.2.3",
52
51
  "eslint": "8.57.1",
53
52
  "events": "^3.3.0",
54
53
  "https-browserify": "^1.0.0",
55
54
  "husky": "8.0.3",
56
- "jest": "30.1.3",
55
+ "jest": "30.4.2",
57
56
  "jest-canvas-mock": "2.5.2",
58
- "jest-environment-jsdom": "30.1.2",
57
+ "jest-environment-jsdom": "30.4.1",
59
58
  "jest-fetch-mock": "^3.0.3",
60
- "jsdoc": "4.0.2",
61
- "parcel": "^2.15.4",
59
+ "jsdoc": "4.0.5",
60
+ "parcel": "^2.16.4",
62
61
  "process": "^0.11.10",
63
- "sass": "1.69.4",
62
+ "sass": "1.101.0",
64
63
  "stream-browserify": "^3.0.0",
65
64
  "stream-http": "^3.2.0",
66
65
  "timers-browserify": "^2.0.12",
67
- "url": "^0.11.3",
66
+ "url": "^0.11.4",
68
67
  "use-resize-observer": "^9.1.0",
69
68
  "util": "^0.12.5"
70
69
  },
71
70
  "peerDependencies": {
72
- "@cesium/engine": "^23.0.0"
71
+ "@cesium/engine": "^26.0.0"
73
72
  },
74
73
  "engines": {
75
74
  "node": "^22.19.0 || ^24.11.1",
package/publiccode.yml CHANGED
@@ -81,8 +81,8 @@ maintenance:
81
81
  name: Masterportal
82
82
  platforms:
83
83
  - web
84
- releaseDate: '2026-04-21'
84
+ releaseDate: '2026-07-17'
85
85
  roadmap: 'https://bitbucket.org/geowerkstatt-hamburg/masterportalapi/src/master/'
86
86
  softwareType: api
87
- softwareVersion: 2.61.0
87
+ softwareVersion: 2.63.0
88
88
  url: 'https://bitbucket.org/geowerkstatt-hamburg/masterportalapi.git'
@@ -202,7 +202,7 @@ async function jobResults (processUrl, jobId, format = "json") {
202
202
  * @param {number?} [pollParameters.timeout=5000] – how long to try polling in ms
203
203
  * @param {number?} [pollParameters.stallFor=100] – how long to wait between retries
204
204
  * @returns {object} job results as json
205
- * @throws
205
+ * @throws {Error}
206
206
  */
207
207
  async function pollJobResults (processUrl, jobId, {timeout = 5000, stallFor = 100} = {}) {
208
208
  const start = Date.now();
@@ -146,16 +146,16 @@ Map.prototype.addLayer = addLayer;
146
146
  * @param {object} [settings.mapParams] - additional parameter object that is spread into the ol.Map constructor object
147
147
  * @param {function} [settings.callback] - optional callback for layer list loading
148
148
  * @param {function} [settings.errorCallback] – method called on error events
149
- * @param {String} [mapMode = "2D"] The map mode. '2D' to craete a 2D-map and '3D' to create a 3D-map.
149
+ * @param {class} [OlMap] - The OpenLayers map class
150
150
  * @returns {object} map object from ol
151
151
  */
152
- export function createMap (config = defaults, {mapParams, callback, errorCallback} = {}) {
152
+ export function createMap (config = defaults, {mapParams, callback, errorCallback} = {}, OlMap = Map) {
153
153
  crs.registerProjections(config.namedProjections);
154
154
  setBackgroundImage(config);
155
155
  setGazetteerUrl(config.gazetteerUrl);
156
156
 
157
157
  const selectedInteractions = Object.assign({}, {dragPan: false, altShiftDragRotate: false, pinchRotate: false}, config.mapInteractions?.interactionModes),
158
- map = new Map(Object.assign({
158
+ map = new OlMap(Object.assign({
159
159
  target: config.target || defaults.target,
160
160
  interactions: olDefaultInteractions(selectedInteractions).extend([
161
161
  new DragPan({
@@ -2,7 +2,7 @@
2
2
  * @module olcs.WMSRasterSynchronizer
3
3
  */
4
4
  import olLayerGroup from "ol/layer/Group.js";
5
- import {getUid} from "olcs/lib/olcs/util.js";
5
+ import {getUid} from "olcs/util.js";
6
6
  import TileWMS from "ol/source/TileWMS.js";
7
7
  import ImageWMS from "ol/source/ImageWMS.js";
8
8
  import WMTS from "ol/source/WMTS.js";
@@ -1,11 +1,9 @@
1
- import xml2js from "xml2js";
2
-
3
1
  import {searchTypes} from "./types";
4
2
  import {getShowGeographicIdentifier} from "./showGeographicIdentifier";
5
3
 
6
4
  /**
7
- * Flattens xml2js result a little to avoid unnecessary one-element arrays.
8
- * NOTE currently keeping special properties ($, _) of xml2js to keep attributes.
5
+ * Flattens result a little to avoid unnecessary one-element arrays.
6
+ * NOTE currently keeping special properties ($, _) to keep attributes.
9
7
  * @param {*} a anything
10
8
  * @returns {*} a's first element if one-element array, else a
11
9
  * @ignore
@@ -40,7 +38,7 @@ function findKey (o, key) {
40
38
 
41
39
  /**
42
40
  * Simply copies over all properties it can find.
43
- * @param {object} entry xml2js-parsed result entry
41
+ * @param {object} entry parsed result entry
44
42
  * @returns {object} copied and flattened properties
45
43
  * @ignore
46
44
  */
@@ -73,7 +71,7 @@ function parseCoordinatesFromWkt (wkt) {
73
71
 
74
72
  /**
75
73
  * Parses result entries; uses type-specific rules to determine name/geometry.
76
- * @param {object} entry xml2js-parsed result entry
74
+ * @param {object} entry parsed result entry
77
75
  * @param {string} type type string
78
76
  * @returns {SearchResult} parsed search result for districts
79
77
  * @ignore
@@ -162,6 +160,72 @@ function parseEntry (entry, type) {
162
160
  return searchResult;
163
161
  }
164
162
 
163
+ /**
164
+ * Converts a DOM element to an object structure.
165
+ * Strips namespace prefixes from tag names.
166
+ * @param {Element} node DOM element to convert
167
+ * @returns {object|string} converted object or text content
168
+ * @ignore
169
+ */
170
+ function domNodeToObject (node) {
171
+ const elementChildren = Array.from(node.children),
172
+ obj = {};
173
+
174
+ // handle attributes
175
+ if (node.attributes.length > 0) {
176
+ obj.$ = {};
177
+ for (const attr of node.attributes) {
178
+ obj.$[attr.localName] = attr.value;
179
+ }
180
+ }
181
+
182
+ if (elementChildren.length === 0) {
183
+ const text = node.textContent?.trim() ?? "";
184
+
185
+ if (obj.$) {
186
+ obj._ = text;
187
+ return obj;
188
+ }
189
+ return text;
190
+ }
191
+
192
+ elementChildren.forEach(child => {
193
+ const key = child.localName,
194
+ value = domNodeToObject(child);
195
+
196
+ if (obj[key]) {
197
+ obj[key].push(value);
198
+ }
199
+ else {
200
+ obj[key] = [value];
201
+ }
202
+ });
203
+
204
+ return obj;
205
+ }
206
+
207
+ /**
208
+ * Parses an XML string using DOMParser.
209
+ * @param {string} xmlString xml to parse
210
+ * @returns {object} parsed object structure
211
+ * @ignore
212
+ */
213
+ function parseXmlString (xmlString) {
214
+ const parser = new DOMParser(),
215
+ doc = parser.parseFromString(xmlString, "text/xml"),
216
+ parseError = doc.querySelector("parsererror");
217
+
218
+ if (parseError) {
219
+ throw new Error(`XML parse error: ${parseError.textContent}`);
220
+ }
221
+
222
+ const root = doc.documentElement,
223
+ result = {};
224
+
225
+ result[root.localName] = domNodeToObject(root);
226
+ return result;
227
+ }
228
+
165
229
  /**
166
230
  * Parses gazetteer xml to search objects.
167
231
  * @param {string} key internal name for query froms searchTypes
@@ -171,26 +235,20 @@ function parseEntry (entry, type) {
171
235
  */
172
236
  export function parse (key, xmlString) {
173
237
  return new Promise((resolve, reject) => {
174
- // tag processor "stripPrefix" removes e.g. "dog:" prefix on properties
175
- xml2js.parseString(xmlString, {tagNameProcessors: [xml2js.processors.stripPrefix]},
176
- (err, source) => {
177
- if (err) {
178
- reject(err);
179
- return;
180
- }
181
-
182
- try {
183
- const searchResults = source.FeatureCollection?.member
184
- ? source.FeatureCollection.member
185
- .map(entry => parseEntry(entry, key))
186
- .sort((a, b) => a.name.localeCompare(b.name))
187
- : [];
188
-
189
- resolve(searchResults);
190
- }
191
- catch (e) {
192
- reject(e);
193
- }
194
- });
238
+ try {
239
+ const source = parseXmlString(xmlString),
240
+ searchResults = source.FeatureCollection?.member
241
+ ? source.FeatureCollection.member
242
+ .map(entry => parseEntry(entry, key))
243
+ .sort((a, b) => a.name.localeCompare(b.name))
244
+ : [];
245
+
246
+ resolve(searchResults);
247
+ }
248
+ catch (e) {
249
+ reject(e);
250
+ }
195
251
  });
196
252
  }
253
+
254
+
@@ -9,6 +9,35 @@ import {getRuleForIndex, getRulesForFeature} from "./lib/getRuleForIndex";
9
9
 
10
10
  const legendsOfAllStyles = [];
11
11
  let legendInformationLength = 0;
12
+ const warnedLayerIdsWithDefaultStyleFallback = new Set();
13
+
14
+ /**
15
+ * Warns once per layer when no styling rule matches and default styles are used.
16
+ * @param {object} [warningContext={}] context for the warning
17
+ * @param {string} [warningContext.layerId] id of the affected layer
18
+ * @returns {void}
19
+ */
20
+ function warnAboutDefaultStyleFallback (warningContext = {}) {
21
+ const {layerId} = warningContext;
22
+
23
+ if (typeof layerId === "string" && layerId.length > 0) {
24
+ if (warnedLayerIdsWithDefaultStyleFallback.has(layerId)) {
25
+ return;
26
+ }
27
+ warnedLayerIdsWithDefaultStyleFallback.add(layerId);
28
+ console.warn(`No valid styling rule found for layer "${layerId}". Falling back to defaults.`);
29
+ return;
30
+ }
31
+ console.warn("No valid styling rule found. Falling back to defaults");
32
+ }
33
+
34
+ /**
35
+ * Clears cached default-style fallback warnings.
36
+ * @returns {void}
37
+ */
38
+ function clearDefaultStyleFallbackWarnings () {
39
+ warnedLayerIdsWithDefaultStyleFallback.clear();
40
+ }
12
41
 
13
42
  /**
14
43
  * Returns true if feature contains some kind of MultiGeometry
@@ -152,9 +181,10 @@ function getMultiGeometryStyle (geometryType, feature, rules, isClustered, wfsIm
152
181
  * @param {object[]} rules styling rules to check. Array can be empty.
153
182
  * @param {Boolean} isClustered Flag to show if feature is clustered.
154
183
  * @param {String} wfsImgPathFromConfig path to wfsImg from Config
184
+ * @param {object} [warningContext={}] context for warning deduplication
155
185
  * @returns {ol/style/Style} style is always returned
156
186
  */
157
- function getGeometryStyle (feature, rules, isClustered, wfsImgPathFromConfig) {
187
+ function getGeometryStyle (feature, rules, isClustered, wfsImgPathFromConfig, warningContext = {}) {
158
188
  const geometryType = feature ? feature.getGeometry().getType() : "cesium";
159
189
 
160
190
  // For simple geometries the first styling rule is used.
@@ -168,7 +198,7 @@ function getGeometryStyle (feature, rules, isClustered, wfsImgPathFromConfig) {
168
198
  }
169
199
 
170
200
  // fall back to default styles as configured in geomType specific styles, if no rule is matched
171
- console.warn("No valid styling rule found. Falling back to defaults");
201
+ warnAboutDefaultStyleFallback(warningContext);
172
202
  return isMultiGeometry(geometryType)
173
203
  ? getMultiGeometryStyle(geometryType, feature, undefined, isClustered, wfsImgPathFromConfig)
174
204
  : getSimpleGeometryStyle(geometryType, feature, undefined, isClustered, wfsImgPathFromConfig);
@@ -224,14 +254,15 @@ function captureLegendFromFeature (styleId, legendInformation) {
224
254
  * @param {ol/feature} feature the feature to style
225
255
  * @param {Boolean} isClustered is feature clustered
226
256
  * @param {String} wfsImgPathFromConfig path to wfsImg from Config
227
- * @returns {ol/style/Style} style used in layer model
257
+ * @param {object} [warningContext={}] context for warning deduplication
258
+ * @returns {ol/style/Style} style used in layer model
228
259
  */
229
- function createStyle (styleObject, feature, isClustered, wfsImgPathFromConfig) {
260
+ function createStyle (styleObject, feature, isClustered, wfsImgPathFromConfig, warningContext = {}) {
230
261
  const rules = getRulesForFeature(styleObject, feature),
231
262
  // Takes first rule in array for labeling so that is giving precedence to the order in the style.json
232
263
  style = Array.isArray(rules) && rules.length > 0 ? rules[0].style : null,
233
264
  hasLabelField = style?.labelField,
234
- geometryStyle = getGeometryStyle(feature, rules, isClustered, wfsImgPathFromConfig),
265
+ geometryStyle = getGeometryStyle(feature, rules, isClustered, wfsImgPathFromConfig, warningContext),
235
266
  legendInformation = Array.isArray(geometryStyle) ? geometryStyle[0].legendInfos : geometryStyle.legendInfos;
236
267
  let styleObjectGeometry = null;
237
268
 
@@ -327,5 +358,6 @@ export default {
327
358
  getGeometryStyle,
328
359
  getLabelStyle,
329
360
  createStyle,
361
+ clearDefaultStyleFallbackWarnings,
330
362
  returnLegendByStyleId
331
363
  };
@@ -106,9 +106,12 @@ class StyleClass {
106
106
  else if (rule?.conditions) {
107
107
  let label = "";
108
108
 
109
- if (rule.conditions?.properties) {
109
+ if (rule.conditions?.properties && !Array.isArray(rule.conditions.properties)) {
110
110
  label = Object.values(rule.conditions.properties).join(", ");
111
111
  }
112
+ if (Array.isArray(rule.conditions?.properties)) {
113
+ label = rule.conditions.properties.map(prop => prop.value).join(", ");
114
+ }
112
115
 
113
116
  if (rule.conditions?.sequence
114
117
  && Array.isArray(rule.conditions.sequence)
@@ -117,7 +120,6 @@ class StyleClass {
117
120
  && rule.conditions.sequence[1] >= rule.conditions.sequence[0]) {
118
121
  label = label + " (" + rule.conditions.sequence.join("-") + ")";
119
122
  }
120
-
121
123
  return label;
122
124
  }
123
125
 
@@ -106,5 +106,42 @@ describe("searchAddress", function () {
106
106
  done();
107
107
  });
108
108
  });
109
+
110
+ describe("domNodeToObject - strips namespace prefixes from tag names", () => {
111
+ it("should not contain colon in any parsed key", async () => {
112
+ const x = await parse(searchTypes.DISTRICT, gazetteerSearchResults.districtResult);
113
+ // eslint-disable-next-line func-style
114
+ const allKeys = (obj) => {
115
+ if (typeof obj !== "object" || obj === null) {
116
+ return [];
117
+ }
118
+ // eslint-disable-next-line max-nested-callbacks
119
+ return Object.keys(obj).flatMap(k => [k, ...allKeys(obj[k])]);
120
+ };
121
+
122
+ allKeys(x[0]).forEach(key => {
123
+ expect(key).not.toContain(":");
124
+ });
125
+ });
126
+
127
+ it("should parse namespace-prefixed XML and return localName as key", () => {
128
+ const parser = new DOMParser();
129
+ const xml = `
130
+ <wfs:FeatureCollection xmlns:wfs="http://www.opengis.net/wfs"
131
+ xmlns:gml="http://www.opengis.net/gml">
132
+ <wfs:member>
133
+ <gml:Point>
134
+ <gml:pos>10.0 53.5</gml:pos>
135
+ </gml:Point>
136
+ </wfs:member>
137
+ </wfs:FeatureCollection>`;
138
+ const doc = parser.parseFromString(xml, "text/xml");
139
+
140
+ expect(doc.documentElement.localName).toBe("FeatureCollection");
141
+ expect(doc.documentElement.children[0].localName).toBe("member");
142
+ expect(doc.documentElement.children[0].children[0].localName).toBe("Point");
143
+ expect(doc.documentElement.children[0].children[0].children[0].localName).toBe("pos");
144
+ });
145
+ });
109
146
  });
110
147
  });
@@ -0,0 +1,70 @@
1
+ import StyleClass from "../../../src/vectorStyle/styles/style";
2
+
3
+ describe("StyleClass", () => {
4
+ describe("createLegendLabel", () => {
5
+ it("returns legendValue with highest precedence", () => {
6
+ const style = new StyleClass(),
7
+ label = style.createLegendLabel({
8
+ conditions: {
9
+ properties: {
10
+ category: "Rule Label"
11
+ }
12
+ }
13
+ },
14
+ {
15
+ attributes: {
16
+ legendValue: 123
17
+ }
18
+ });
19
+
20
+ expect(label).toBe("123");
21
+ });
22
+
23
+ it("creates label from properties configured as array of objects", () => {
24
+ const style = new StyleClass(),
25
+ label = style.createLegendLabel({
26
+ conditions: {
27
+ properties: [
28
+ {
29
+ key: "category",
30
+ value: "A"
31
+ },
32
+ {
33
+ key: "category",
34
+ value: "B"
35
+ }
36
+ ]
37
+ }
38
+ },
39
+ {
40
+ attributes: {}
41
+ });
42
+
43
+ expect(label).toBe("A, B");
44
+ });
45
+
46
+ it("appends valid sequence to created label", () => {
47
+ const style = new StyleClass(),
48
+ label = style.createLegendLabel({
49
+ conditions: {
50
+ properties: {
51
+ category: "A"
52
+ },
53
+ sequence: [10, 20]
54
+ }
55
+ },
56
+ {
57
+ attributes: {}
58
+ });
59
+
60
+ expect(label).toBe("A (10-20)");
61
+ });
62
+
63
+ it("returns null if no legendValue and no rule conditions exist", () => {
64
+ const style = new StyleClass(),
65
+ label = style.createLegendLabel(undefined, {attributes: {}});
66
+
67
+ expect(label).toBeNull();
68
+ });
69
+ });
70
+ });