From f3d49e91f865f509dfd87bb587594cdeadc91a5c Mon Sep 17 00:00:00 2001 From: zhengjitf Date: Tue, 12 Dec 2023 17:46:53 +0800 Subject: [PATCH] fix: enhance all kinds xpath queries and clean code --- .../templating/local-template-tags.ts | 38 +------------ .../insomnia/src/utils/xpath/query.test.ts | 12 +++++ packages/insomnia/src/utils/xpath/query.ts | 54 ++++++++++--------- 3 files changed, 43 insertions(+), 61 deletions(-) diff --git a/packages/insomnia/src/ui/components/templating/local-template-tags.ts b/packages/insomnia/src/ui/components/templating/local-template-tags.ts index e052585ff1a..e60c2ed2188 100644 --- a/packages/insomnia/src/ui/components/templating/local-template-tags.ts +++ b/packages/insomnia/src/ui/components/templating/local-template-tags.ts @@ -6,7 +6,6 @@ import { JSONPath } from 'jsonpath-plus'; import os from 'os'; import { CookieJar } from 'tough-cookie'; import * as uuid from 'uuid'; -import type { SelectedValue } from 'xpath'; import { Request, RequestParameter } from '../../../models/request'; import { Response } from '../../../models/response'; @@ -14,6 +13,7 @@ import { TemplateTag } from '../../../plugins'; import { PluginTemplateTag } from '../../../templating/extensions'; import { invariant } from '../../../utils/invariant'; import { buildQueryStringFromParams, joinUrlAndQueryString, smartEncodeUrl } from '../../../utils/url/querystring'; +import { queryXPath } from '../../../utils/xpath/query'; const localTemplatePlugins: { templateTag: PluginTemplateTag }[] = [ { @@ -707,41 +707,7 @@ const localTemplatePlugins: { templateTag: PluginTemplateTag }[] = [ return results[0]; } } else { - const DOMParser = (await import('xmldom')).DOMParser; - const dom = new DOMParser().parseFromString(body); - let selectedValues: SelectedValue[] = []; - if (sanitizedFilter === undefined) { - throw new Error('Must pass an XPath query.'); - } - try { - selectedValues = (await import('xpath')).select(sanitizedFilter, dom); - } catch (err) { - throw new Error(`Invalid XPath query: ${sanitizedFilter}`); - } - let results: { outer: string; inner: string | null }[] = []; - - // Functions return plain strings - if (typeof selectedValues === 'string') { - results = [{ outer: selectedValues, inner: selectedValues }]; - } - - results = (selectedValues as Node[]) - .filter(sv => sv.nodeType === Node.ATTRIBUTE_NODE - || sv.nodeType === Node.ELEMENT_NODE - || sv.nodeType === Node.TEXT_NODE) - .map(selectedValue => { - const outer = selectedValue.toString().trim(); - if (selectedValue.nodeType === Node.ATTRIBUTE_NODE) { - return { outer, inner: selectedValue.nodeValue }; - } - if (selectedValue.nodeType === Node.ELEMENT_NODE) { - return { outer, inner: selectedValue.childNodes.toString() }; - } - if (selectedValue.nodeType === Node.TEXT_NODE) { - return { outer, inner: selectedValue.toString().trim() }; - } - return { outer, inner: null }; - }); + const results = queryXPath(body, sanitizedFilter); if (results.length === 0) { throw new Error(`Returned no results: ${sanitizedFilter}`); diff --git a/packages/insomnia/src/utils/xpath/query.test.ts b/packages/insomnia/src/utils/xpath/query.test.ts index fe0e2353ba9..dac9a685ec9 100644 --- a/packages/insomnia/src/utils/xpath/query.test.ts +++ b/packages/insomnia/src/utils/xpath/query.test.ts @@ -28,6 +28,18 @@ describe('queryXPath()', () => { ]); }); + it('handles number query', () => { + expect(queryXPath('1/x>', 'number(//y[1])')).toEqual([ + { inner: 1, outer: 1 }, + ]); + }); + + it('handles boolean query', () => { + expect(queryXPath('1', '//y[1]/text() = "1"')).toEqual([ + { inner: true, outer: true }, + ]); + }); + it('handles text() query', () => { expect(queryXPath('HarryPotter', 'local-name(/book)')) .toEqual([{ 'inner': 'book', 'outer': 'book' }]); diff --git a/packages/insomnia/src/utils/xpath/query.ts b/packages/insomnia/src/utils/xpath/query.ts index 757117dc246..d802bfce0f1 100644 --- a/packages/insomnia/src/utils/xpath/query.ts +++ b/packages/insomnia/src/utils/xpath/query.ts @@ -1,41 +1,45 @@ import { DOMParser } from 'xmldom'; -import xpath, { SelectedValue } from 'xpath'; +import xpath from 'xpath'; + +/** + * A revised type for the return value of `xpath.select(expression, node)` + */ +type SelectedValue = string | number | boolean | Node[]; /** * Query an XML blob with XPath */ export const queryXPath = (xml: string, query?: string) => { - const dom = new DOMParser().parseFromString(xml); - let selectedValues: SelectedValue[] = []; if (query === undefined) { throw new Error('Must pass an XPath query.'); } + const dom = new DOMParser().parseFromString(xml); + let selectedValues: SelectedValue = []; try { - selectedValues = xpath.select(query, dom); + selectedValues = xpath.select(query, dom) as SelectedValue; } catch (err) { throw new Error(`Invalid XPath query: ${query}`); } - // Functions return plain strings - if (typeof selectedValues === 'string') { - return [{ outer: selectedValues, inner: selectedValues }]; - } - return (selectedValues as Node[]) - .filter(sv => sv.nodeType === Node.ATTRIBUTE_NODE - || sv.nodeType === Node.ELEMENT_NODE - || sv.nodeType === Node.TEXT_NODE) - .map(selectedValue => { - const outer = selectedValue.toString().trim(); - if (selectedValue.nodeType === Node.ATTRIBUTE_NODE) { - return { outer, inner: selectedValue.nodeValue }; - } - if (selectedValue.nodeType === Node.ELEMENT_NODE) { - return { outer, inner: selectedValue.childNodes.toString() }; - } - if (selectedValue.nodeType === Node.TEXT_NODE) { - return { outer, inner: selectedValue.toString().trim() }; - } - return { outer, inner: null }; - }); + if (Array.isArray(selectedValues)) { + return selectedValues + .filter(sv => sv.nodeType === Node.ATTRIBUTE_NODE + || sv.nodeType === Node.ELEMENT_NODE + || sv.nodeType === Node.TEXT_NODE) + .map(selectedValue => { + const outer = selectedValue.toString().trim(); + if (selectedValue.nodeType === Node.ATTRIBUTE_NODE) { + return { outer, inner: selectedValue.nodeValue }; + } + if (selectedValue.nodeType === Node.ELEMENT_NODE) { + return { outer, inner: selectedValue.childNodes.toString() }; + } + if (selectedValue.nodeType === Node.TEXT_NODE) { + return { outer, inner: selectedValue.toString().trim() }; + } + return { outer, inner: null }; + }); + } + return [{ outer: selectedValues, inner: selectedValues }]; };