diff --git a/plugins/template-function-xml/package.json b/plugins/template-function-xml/package.json index c54a09279..f476cc0a2 100644 --- a/plugins/template-function-xml/package.json +++ b/plugins/template-function-xml/package.json @@ -8,7 +8,8 @@ "types": "src/index.ts", "scripts": { "build": "yaakcli build", - "dev": "yaakcli dev" + "dev": "yaakcli dev", + "test": "vp test --run tests" }, "dependencies": { "@xmldom/xmldom": "^0.9.12", diff --git a/plugins/template-function-xml/src/index.ts b/plugins/template-function-xml/src/index.ts index 1d3ec5c99..6bb15b9da 100755 --- a/plugins/template-function-xml/src/index.ts +++ b/plugins/template-function-xml/src/index.ts @@ -77,11 +77,19 @@ export function filterXPath( return String(items); } if (!Array.isArray(items) || result === "first") { - return items[0] != null ? String(items[0].firstChild ?? "") : ""; + return items[0] != null ? nodeText(items[0]) : ""; } if (result === "join") { - return items.map((item) => String(item.firstChild ?? "")).join(join ?? ""); + return items.map(nodeText).join(join ?? ""); } // Not sure what cases this happens in (?) return String(items); } + +function nodeText(node: Node): string { + // Attribute, text and CDATA nodes have no children, so their value is on the node itself + if (xpath.isAttribute(node) || xpath.isTextNode(node) || xpath.isCDATASection(node)) { + return node.nodeValue ?? ""; + } + return String(node.firstChild ?? ""); +} diff --git a/plugins/template-function-xml/tests/xpath.test.ts b/plugins/template-function-xml/tests/xpath.test.ts new file mode 100644 index 000000000..8aba8d2a3 --- /dev/null +++ b/plugins/template-function-xml/tests/xpath.test.ts @@ -0,0 +1,36 @@ +import type { Context } from "@yaakapp/api"; +import { describe, expect, it } from "vite-plus/test"; +import { filterXPath, plugin } from "../src"; + +const USERS = + 'AliceBob'; + +describe("xml.xpath", () => { + const xpathFunction = plugin.templateFunctions?.find((f) => f.name === "xml.xpath"); + + const select = async (query: string, result = "first", join = ", ") => + await xpathFunction?.onRender({} as Context, { + values: { input: USERS, query, result, join }, + purpose: "send", + }); + + it("returns the text of a selected element", async () => { + expect(await select("//user/name")).toBe("Alice"); + }); + + it("returns the value of a selected attribute", async () => { + expect(await select("//user/@id")).toBe("42"); + }); + + it("joins the values of selected attributes", async () => { + expect(await select("//user/@id", "join")).toBe("42, 7"); + }); + + it("returns the value of a selected text node", async () => { + expect(await select("//user/name/text()")).toBe("Alice"); + }); + + it("returns the value of a selected CDATA section", () => { + expect(filterXPath("", "/a/text()", "first", null)).toBe("x