From 70a292d483baa51e4b52fba913150bc1f3772672 Mon Sep 17 00:00:00 2001 From: Jan Kumer Date: Tue, 30 Jul 2024 12:58:54 +0200 Subject: [PATCH] Replace xml2js with fast-xml-parser --- package-lock.json | 96 ++++++++++++++++++++++++----------------- package.json | 6 +-- src/layer/const.ts | 11 +++++ src/layer/utils.ts | 19 +++++--- src/layer/wmts.utils.ts | 22 ++++++---- 5 files changed, 96 insertions(+), 58 deletions(-) diff --git a/package-lock.json b/package-lock.json index 45853928..eab212d1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,7 +16,6 @@ "@types/jest": "^25.1.3", "@types/node-fetch": "^2.5.7", "@types/service-worker-mock": "^2.0.4", - "@types/xml2js": "^0.4.4", "@typescript-eslint/eslint-plugin": "^7.17.0", "@typescript-eslint/parser": "^7.17.0", "axios-mock-adapter": "^1.18.1", @@ -40,18 +39,20 @@ "typedoc": "^0.26.5", "typescript": "5.5.4" }, + "engines": { + "node": ">=18" + }, "peerDependencies": { "@turf/area": "^6.0.1", "@turf/helpers": "^6.1.4", "@types/proj4": "^2.5.2", - "@types/xml2js": "^0.4.4", "axios": "^0.21.1", + "fast-xml-parser": "^4.4.1", "moment": "^2.24.0", "polygon-clipping": "^0.14.3", "proj4": "^2.9.0", "query-string": "^6.4.2", - "terraformer-wkt-parser": "^1.2.1", - "xml2js": "^0.4.19" + "terraformer-wkt-parser": "^1.2.1" } }, "node_modules/@ampproject/remapping": { @@ -4415,15 +4416,6 @@ "node": ">=0.10.0" } }, - "node_modules/@types/xml2js": { - "version": "0.4.14", - "resolved": "https://registry.npmjs.org/@types/xml2js/-/xml2js-0.4.14.tgz", - "integrity": "sha512-4YnrRemBShWRO2QjvUin8ESA41rH+9nQGLUGZV/1IDhi3SL9OhdpNC/MrulTWuptXKwhx/aDxE7toV0f/ypIXQ==", - "dev": true, - "dependencies": { - "@types/node": "*" - } - }, "node_modules/@types/yargs": { "version": "17.0.32", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.32.tgz", @@ -10236,6 +10228,28 @@ "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", "dev": true }, + "node_modules/fast-xml-parser": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.4.1.tgz", + "integrity": "sha512-xkjOecfnKGkSsOwtZ5Pz7Us/T6mrbPQrq0nh+aCO5V9nk5NLWmasAHumTKjiPJPWANe+kAZ84Jc8ooJkzZ88Sw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + }, + { + "type": "paypal", + "url": "https://paypal.me/naturalintelligence" + } + ], + "peer": true, + "dependencies": { + "strnum": "^1.0.5" + }, + "bin": { + "fxparser": "src/cli/cli.js" + } + }, "node_modules/fastparse": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/fastparse/-/fastparse-1.1.2.tgz", @@ -19447,11 +19461,29 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "dev": true }, - "node_modules/sax": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz", - "integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==", - "peer": true + "node_modules/sane/node_modules/micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "extraneous": true, + "dependencies": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } }, "node_modules/saxes": { "version": "6.0.0", @@ -20545,6 +20577,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/strnum": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-1.0.5.tgz", + "integrity": "sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==", + "peer": true + }, "node_modules/style-loader": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-1.3.0.tgz", @@ -22646,28 +22684,6 @@ "node": ">=12" } }, - "node_modules/xml2js": { - "version": "0.4.23", - "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz", - "integrity": "sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==", - "peer": true, - "dependencies": { - "sax": ">=0.6.0", - "xmlbuilder": "~11.0.0" - }, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/xmlbuilder": { - "version": "11.0.1", - "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", - "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==", - "peer": true, - "engines": { - "node": ">=4.0" - } - }, "node_modules/xmlchars": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", diff --git a/package.json b/package.json index 846adfa3..eceb590f 100644 --- a/package.json +++ b/package.json @@ -9,14 +9,13 @@ "@turf/area": "^6.0.1", "@turf/helpers": "^6.1.4", "@types/proj4": "^2.5.2", - "@types/xml2js": "^0.4.4", "axios": "^0.21.1", + "fast-xml-parser": "^4.4.1", "moment": "^2.24.0", "polygon-clipping": "^0.14.3", "proj4": "^2.9.0", "query-string": "^6.4.2", - "terraformer-wkt-parser": "^1.2.1", - "xml2js": "^0.4.19" + "terraformer-wkt-parser": "^1.2.1" }, "engines": { "node": ">=18" @@ -30,7 +29,6 @@ "@types/jest": "^25.1.3", "@types/node-fetch": "^2.5.7", "@types/service-worker-mock": "^2.0.4", - "@types/xml2js": "^0.4.4", "@typescript-eslint/eslint-plugin": "^7.17.0", "@typescript-eslint/parser": "^7.17.0", "axios-mock-adapter": "^1.18.1", diff --git a/src/layer/const.ts b/src/layer/const.ts index 92f2c21c..44c70f12 100644 --- a/src/layer/const.ts +++ b/src/layer/const.ts @@ -286,3 +286,14 @@ export const PLANET_FALSE_COLOR_TEMPLATES = [ export const EQUATOR_RADIUS = 6378137.0; export const DEGREE_TO_RADIAN = Math.PI / 180; export const RADIAN_TO_DEGREE = 180 / Math.PI; + +export const XmlParserOptions = Object.freeze({ + attributesGroupName: '$', + attributeNamePrefix: '', + textNodeName: '_', + ignoreAttributes: false, + isArray: (name: string, jpath: string, isLeafNode: boolean, isAttribute: boolean) => { + const isA = !isAttribute && !['Capabilities', 'WMS_Capabilities', 'WMT_MS_Capabilities'].includes(name); + return isA; + }, +}); diff --git a/src/layer/utils.ts b/src/layer/utils.ts index cd8d7148..da029f7b 100644 --- a/src/layer/utils.ts +++ b/src/layer/utils.ts @@ -1,14 +1,23 @@ import axios, { AxiosRequestConfig } from 'axios'; import { stringify, parseUrl, stringifyUrl } from 'query-string'; -import { parseStringPromise } from 'xml2js'; -import { EQUATOR_RADIUS, OgcServiceTypes, SH_SERVICE_HOSTNAMES_V3, SH_SERVICE_ROOT_URL } from './const'; + import { getAxiosReqParams, RequestConfiguration } from '../utils/cancelRequests'; import { CACHE_CONFIG_30MIN, CACHE_CONFIG_30MIN_MEMORY } from '../utils/cacheHandlers'; -import type { GetCapabilitiesWmtsXml } from './wmts.utils'; +import { XMLParser } from 'fast-xml-parser'; +import proj4 from 'proj4'; import { getAuthToken } from '../auth'; import { BBox } from '../bbox'; import { CRS_EPSG3857 } from '../crs'; -import proj4 from 'proj4'; +import { + EQUATOR_RADIUS, + OgcServiceTypes, + SH_SERVICE_HOSTNAMES_V3, + SH_SERVICE_ROOT_URL, + XmlParserOptions, +} from './const'; +import { GetCapabilitiesWmtsXml } from './wmts.utils'; + +export const xmlParser = new XMLParser(XmlParserOptions); interface Capabilities { Service: []; @@ -67,7 +76,7 @@ export async function fetchGetCapabilitiesXml( }; const url = createGetCapabilitiesXmlUrl(baseUrl, ogcServiceType); const res = await axios.get(url, axiosReqConfig); - const parsedXml = await parseStringPromise(res.data); + const parsedXml = xmlParser.parse(res.data); return parsedXml; } diff --git a/src/layer/wmts.utils.ts b/src/layer/wmts.utils.ts index 0e9581b8..b8505e20 100644 --- a/src/layer/wmts.utils.ts +++ b/src/layer/wmts.utils.ts @@ -163,15 +163,19 @@ export function toPixel( } function parseXmlWmtsLayers(parsedXml: GetCapabilitiesWmtsXml): GetCapabilitiesXmlLayer[] { - return parsedXml.Capabilities.Contents[0].Layer.map((l) => { - return { - Name: l['ows:Identifier'], - Title: l['ows:Title'], - Abstract: l['ows:Abstract'], - Style: l.Style, - ResourceUrl: getResourceUrl(l), - }; - }); + try { + return parsedXml.Capabilities.Contents[0].Layer.map((l) => { + return { + Name: l['ows:Identifier'], + Title: l['ows:Title'], + Abstract: l['ows:Abstract'], + Style: l.Style, + ResourceUrl: getResourceUrl(l), + }; + }); + } catch (x) { + console.error(x); + } } export async function fetchLayersFromWmtsGetCapabilitiesXml( baseUrl: string,