UNPKG

@bmqube/xmlrpc

Version:

A pure TypeScript XML-RPC client and server. Forked from (https://github.com/baalexander/node-xmlrpc)

192 lines (191 loc) 6.14 kB
// serializer.mts import * as xmlBuilder from "xmlbuilder"; import dateFormatter from "./date_formatter.mjs"; import CustomType from "./customtype.mjs"; /** * Creates the XML for an XML-RPC method call. */ export function serializeMethodCall(method, paramsInput, encoding) { const params = paramsInput ?? []; const options = { version: "1.0", allowSurrogateChars: true }; if (encoding) options.encoding = encoding; const xml = xmlBuilder.create("methodCall", options) .ele("methodName") .txt(method) .up() .ele("params"); params.forEach((param) => { serializeValue(param, xml.ele("param")); }); // Includes the <?xml ...> declaration return xml.doc().toString(); } /** * Creates the XML for an XML-RPC method response. */ export function serializeMethodResponse(result) { const xml = xmlBuilder.create("methodResponse", { version: "1.0", allowSurrogateChars: true, }) .ele("params") .ele("param"); serializeValue(result, xml); return xml.doc().toString(); } /** * Creates the XML for an XML-RPC fault response. */ export function serializeFault(fault) { const xml = xmlBuilder.create("methodResponse", { version: "1.0", allowSurrogateChars: true, }).ele("fault"); serializeValue(fault, xml); return xml.doc().toString(); } function serializeValue(value, xml) { const stack = [{ value, xml }]; let current = null; let valueNode = null; let next = null; while (stack.length > 0) { current = stack[stack.length - 1]; if (current.index !== undefined) { // Iterating a compound next = getNextItemsFrame(current); if (next) { stack.push(next); } else { stack.pop(); } } else { // about to add a new value (compound or simple) valueNode = current.xml.ele("value"); switch (typeof current.value) { case "boolean": appendBoolean(current.value, valueNode); stack.pop(); break; case "string": appendString(current.value, valueNode); stack.pop(); break; case "number": appendNumber(current.value, valueNode); stack.pop(); break; case "object": if (current.value === null) { valueNode.ele("nil"); stack.pop(); } else if (current.value instanceof Date) { appendDatetime(current.value, valueNode); stack.pop(); } else if (Buffer.isBuffer(current.value)) { appendBuffer(current.value, valueNode); stack.pop(); } else if (current.value instanceof CustomType) { current.value.serialize(valueNode); stack.pop(); } else { if (Array.isArray(current.value)) { current.xml = valueNode.ele("array").ele("data"); } else { current.xml = valueNode.ele("struct"); current.keys = Object.keys(current.value); } current.index = 0; next = getNextItemsFrame(current); if (next) { stack.push(next); } else { stack.pop(); } } break; default: // unsupported value types are ignored (parity with original) stack.pop(); break; } } } } function getNextItemsFrame(frame) { let nextFrame = null; if (frame.keys) { if (frame.index < frame.keys.length) { const key = frame.keys[frame.index++]; const member = frame.xml.ele("member").ele("name"); // original used .text(key) then .up() (member.text ? member.text(key) : member.txt(key)).up?.(); nextFrame = { value: frame.value[key], xml: member, // the JS original attaches next value node at member-level }; } } else if (frame.index < frame.value.length) { nextFrame = { value: frame.value[frame.index], xml: frame.xml, }; frame.index = frame.index + 1; } return nextFrame; } function appendBoolean(value, xml) { xml.ele("boolean").txt(value ? 1 : 0); } const illegalChars = /^(?![^<&]*]]>[^<&]*)[^<&]*$/; function appendString(value, xml) { if (value.length === 0) { xml.ele("string"); return; } if (!illegalChars.test(value)) { const str = xml.ele("string"); if (typeof str.d === "function") { str.d(value); } else if (typeof str.cdata === "function") { str.cdata(value); } else { str.txt(value); } return; } xml.ele("string").txt(value); } function appendNumber(value, xml) { if (value % 1 === 0) { xml.ele("int").txt(value); } else { xml.ele("double").txt(value); } } function appendDatetime(value, xml) { xml.ele("dateTime.iso8601").txt(dateFormatter.encodeIso8601(value)); } function appendBuffer(value, xml) { xml.ele("base64").txt(value.toString("base64")); } // Default export that mirrors the CommonJS `module.exports = { ... }` style const Serializer = { serializeMethodCall, serializeMethodResponse, serializeFault, }; export default Serializer;