UNPKG

@enonic/mock-xp

Version:

Mock Enonic XP API JavaScript Library

189 lines (163 loc) 4.27 kB
import type { Node } from '@enonic-types/lib-node'; import type { NodeXML } from './NodeXML.d'; import type { Log } from '../../types'; import { XMLParser } from 'fast-xml-parser'; import { forceArray } from '@enonic/js-utils/array/forceArray'; import { sortKeys } from '@enonic/js-utils/object/sortKeys'; interface ParsedObject { [key: string]: any; } function transform(obj: ParsedObject, tag?: string): any { if (obj === null || typeof obj !== 'object') { return obj; } if (Array.isArray(obj)) { return obj.map((item) => transform(item, tag)); } // Handle null values if ('@isNull' in obj && obj['@isNull'] === 'true') { return null; } // Handle arrays marked with type="array" if ('@type' in obj && obj['@type'] === 'array') { if ('value' in obj) { const val = obj['value']; const transformedVal = transform(val, 'value'); return Array.isArray(transformedVal) ? transformedVal : [transformedVal]; } return []; } // Handle leaf nodes with #text if ('#text' in obj) { let value = obj['#text']; switch (tag) { case 'boolean': return value === 'true'; case 'dateTime': // return new Date(value); // TODO This is probably wrong... case 'string': case 'reference': case 'localTime': // if any case 'long': // if any, parseInt return value; default: return value; } } // Handle principals with @key if ('@key' in obj) { const result: ParsedObject = { key: obj['@key'] }; for (const k in obj) { if (k !== '@key' && !k.startsWith('@')) { result[k] = transform(obj[k], k); } } return result; } // General container: group by type and @name const result: ParsedObject = {}; for (const type in obj) { if (type.startsWith('@') || type === '#text') continue; let items = Array.isArray(obj[type]) ? obj[type] : [obj[type]]; for (const item of items) { const name = item['@name']; let val = transform(item, type); // Remove @name from val if it's an object if (typeof val === 'object' && val !== null && '@name' in val) { delete val['@name']; } if (name) { // Group by name if (name in result) { if (!Array.isArray(result[name])) { result[name] = [result[name]]; } result[name].push(val); } else { result[name] = val; } } else { // No name, group by type const key = type; if (key in result) { if (!Array.isArray(result[key])) { result[key] = [result[key]]; } result[key].push(val); } else { result[key] = val; } } } } return result; } export function parseEnonicXml< DATA extends Record<string, unknown> = Record<string, unknown> >({ // _debug = false, _trace = false, log, xmlString }: { // _debug?: boolean; _trace?: boolean; log: Log; xmlString: string; }): Node<DATA> { const options = { ignoreAttributes: false, attributeNamePrefix: '@', textNodeName: '#text', parseNodeValue: false, parseAttributeValue: false, trimValues: true, allowBooleanAttributes: true, arrayMode: false, }; const parser = new XMLParser(options); const parsed = parser.parse(xmlString); if (_trace) log.debug('parseEnonicXml parsed:%s', parsed); // Assuming the root is <node>, transform its content const obj = transform(parsed.node) as NodeXML['node']; if (_trace) log.debug('parseEnonicXml obj:%s', obj); const { data, indexConfigs, permissions, timestamp } = obj; delete obj.data; delete obj.indexConfigs; delete obj.permissions; delete obj.timestamp; const _permissions = []; for (const permission of forceArray(permissions)) { const { principal } = permission; for (const { allow, deny, key } of principal) { _permissions.push({ principal: key, allow, deny }); } } const node = { _indexConfig: { // Used same order as datatoolbox, not alphabetical. analyzer: indexConfigs.analyzer, default: indexConfigs.defaultConfig, configs: indexConfigs.pathIndexConfigs.pathIndexConfig?.map(({ indexConfig: config, path, }) => ({ config, path })) || [], }, _permissions, _tz: timestamp, }; for (const attr in obj) { node[`_${attr}`] = obj[attr]; } for (const attr in data) { node[attr] = data[attr]; } return sortKeys(node) as unknown as Node<DATA>; }