xml-parser.tsโข5.08 kB
/**
* XML Parsing Utilities
*
* Uses fast-xml-parser for reliable XML parsing
*
* ๐ TEACHING: Why fast-xml-parser?
* - โ
Correctly handles XML arrays (no phantom elements!)
* - โ
Preserves XML structure accurately
* - โ
Fast and memory-efficient
* - โ
Configurable parsing options
*
* โ ๏ธ CRITICAL: Unlike Jackson XmlMapper, this parser:
* - Returns correct array sizes
* - Doesn't create phantom entries
* - Handles nested structures properly
*/
import { XMLParser, XMLBuilder } from 'fast-xml-parser';
/**
* XML Parser configuration
*/
const parserOptions = {
ignoreAttributes: false, // Keep XML attributes
attributeNamePrefix: '@_', // Prefix for attributes
textNodeName: '#text', // Name for text content
ignoreDeclaration: true, // Ignore <?xml?> declaration
removeNSPrefix: false, // Keep namespace prefixes
parseAttributeValue: true, // Parse attribute values
parseTagValue: true, // Parse tag values
trimValues: true, // Trim whitespace
cdataPropName: '__cdata', // CDATA property name
numberParseOptions: {
leadingZeros: false,
hex: false,
eNotation: false,
},
};
/**
* XML Builder configuration
*/
const builderOptions = {
ignoreAttributes: false,
attributeNamePrefix: '@_',
textNodeName: '#text',
format: false, // Don't format output (compact)
suppressEmptyNode: false, // Keep empty nodes
};
/**
* Create XML parser instance
*/
export const xmlParser = new XMLParser(parserOptions);
/**
* Create XML builder instance
*/
export const xmlBuilder = new XMLBuilder(builderOptions);
/**
* Parse SOAP XML response
*
* Extracts the SOAP Body content and returns parsed object
*
* @param xmlString Raw SOAP XML response
* @returns Parsed response object
*/
export function parseSoapResponse<T = any>(xmlString: string): T {
const parsed = xmlParser.parse(xmlString);
// Navigate SOAP structure: Envelope > Body > [Method]Response
const envelope = parsed['soap:Envelope'] || parsed['soapenv:Envelope'] || parsed.Envelope;
if (!envelope) {
throw new Error('Invalid SOAP response: Missing Envelope');
}
const body = envelope['soap:Body'] || envelope['soapenv:Body'] || envelope.Body;
if (!body) {
throw new Error('Invalid SOAP response: Missing Body');
}
// Check for SOAP Fault
const fault = body['soap:Fault'] || body['soapenv:Fault'] || body.Fault;
if (fault) {
const faultString = fault.faultstring || fault.message || 'Unknown SOAP fault';
throw new Error(`SOAP Fault: ${faultString}`);
}
// Return the response content (first non-fault element in Body)
const keys = Object.keys(body).filter(k => !k.includes('Fault'));
if (keys.length === 0) {
throw new Error('Invalid SOAP response: Empty body');
}
// Get the method response (e.g., get_waybillsResponse)
const methodResponse = body[keys[0]];
// Some responses have a nested result element
// e.g., get_waybillsResponse > get_waybillsResult
if (methodResponse && typeof methodResponse === 'object') {
// Filter out XML attributes (keys starting with @_)
const resultKeys = Object.keys(methodResponse).filter(k => !k.startsWith('@_'));
// Check if there's a Result element
const resultKey = resultKeys.find(k => k.endsWith('Result'));
if (resultKey) {
const result = methodResponse[resultKey];
// Check for RS.ge API error format: <RESULT><STATUS>-1072</STATUS></RESULT>
if (result && typeof result === 'object' && 'RESULT' in result) {
const rsResult = (result as any).RESULT;
if (rsResult && rsResult.STATUS && Number(rsResult.STATUS) < 0) {
const errorCode = rsResult.STATUS;
throw new Error(
`RS.ge API Error ${errorCode}. ` +
`Please check error code documentation or use a valid date range (max 3 days).`
);
}
}
return result as T;
}
}
return methodResponse as T;
}
/**
* Build SOAP XML request
*
* @param method SOAP method name (e.g., 'get_waybills')
* @param params Method parameters
* @param namespace SOAP namespace URI
* @returns SOAP XML string
*/
export function buildSoapRequest(
method: string,
params: Record<string, any>,
namespace: string = 'http://tempuri.org/'
): string {
const soapEnvelope = {
'soap:Envelope': {
'@_xmlns:xsi': 'http://www.w3.org/2001/XMLSchema-instance',
'@_xmlns:xsd': 'http://www.w3.org/2001/XMLSchema',
'@_xmlns:soap': 'http://schemas.xmlsoap.org/soap/envelope/',
'soap:Body': {
[method]: {
'@_xmlns': namespace,
...params,
},
},
},
};
const xml = xmlBuilder.build(soapEnvelope);
// Add XML declaration
return `<?xml version="1.0" encoding="utf-8"?>${xml}`;
}
/**
* Build nested XML structure for save_waybill
*
* Special handling for complex nested structures
*/
export function buildWaybillXml(waybill: any): string {
return xmlBuilder.build({ WAYBILL: waybill });
}