Skip to content

Commit

Permalink
fix: enhance all kinds xpath queries and clean code
Browse files Browse the repository at this point in the history
  • Loading branch information
zhengjitf committed Dec 20, 2023
1 parent 4a30e4f commit f3d49e9
Show file tree
Hide file tree
Showing 3 changed files with 43 additions and 61 deletions.
Expand Up @@ -6,14 +6,14 @@ 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';
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 }[] = [
{
Expand Down Expand Up @@ -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}`);
Expand Down
12 changes: 12 additions & 0 deletions packages/insomnia/src/utils/xpath/query.test.ts
Expand Up @@ -28,6 +28,18 @@ describe('queryXPath()', () => {
]);
});

it('handles number query', () => {
expect(queryXPath('<x><y>1</y>/x>', 'number(//y[1])')).toEqual([
{ inner: 1, outer: 1 },
]);
});

it('handles boolean query', () => {
expect(queryXPath('<x><y>1</y></x>', '//y[1]/text() = "1"')).toEqual([
{ inner: true, outer: true },
]);
});

it('handles text() query', () => {
expect(queryXPath('<book><title>Harry</title><title>Potter</title></book>', 'local-name(/book)'))
.toEqual([{ 'inner': 'book', 'outer': 'book' }]);
Expand Down
54 changes: 29 additions & 25 deletions 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 }];
};

0 comments on commit f3d49e9

Please sign in to comment.