UNPKG

@sap/xsodata

Version:

Expose data from a HANA database as OData V2 service with help of .xsodata files.

309 lines (262 loc) 8.62 kB
'use strict'; /** * Xml to json serializer. * * This module converts any xml format into json format. * * Usage: * * var xmlStringData = getXmlStringSomeHow().toString('utf-8'); * * var serializer = new XmlToJsonSerializer(xmlStringData, { * logger:logger * }); * * serializer.serialize(function(err, result){ * // handle err if != null * // handle json result * }); * */ var lodash = require('lodash'); var async = require('async'); var utils = require('./../utils/utils'); var parser = require('./../parsers/xml2JsonParser'); module.exports = XmlToJsonSerializer; /** * Constructor. This method instanciates a XmlToJsonSerializer. * * @param xmlString {String} Xml string to parse * @param options {Object} Options like options.logger * @constructor */ function XmlToJsonSerializer(xmlString, options) { if((this instanceof XmlToJsonSerializer) !== true){ return new XmlToJsonSerializer(xmlString, options); } this._options = options; this._input = xmlString; this._middlewares = { before: [], after: [] }; } /** * Execute provided functions before serializing in a try/catch. * * Each function provided will be executed before serializing within * a try/catch block. Function usage: * * function myBeforeFn(context,next){ * // do your stuff * next(err, context); * } * * @chainable * @param fn {Function} Function to be called before serializing * @returns {XmlToJsonSerializer} Current instance of XmlToJsonSerializer */ XmlToJsonSerializer.prototype.before = function before(fn) { this._middlewares.before.push(utils.try(fn)); return this; }; /** * Execute provided functions after serializing in a try/catch. * * Each function provided will be executed after serializing within * a try/catch block. Function usage: * * function myAfterFn(context,asyncDone){ * // do your stuff * asyncDone(err, context); * } * * @chainable * @param fn {Function} Function to be called after serializing * @returns {XmlToJsonSerializer} Current instance of XmlToJsonSerializer */ XmlToJsonSerializer.prototype.after = function after(fn) { this._middlewares.after.push(utils.try(fn)); return this; }; /** * This method starts the serialization process. * * It concatenates all tasks (functions) to run (before, serializing, after) * and calls the provided asyncDone function on end. * Expected asyncDone function: * * function(err, result){ * } * * Result is the native json object as the serialization result. * * @chainable * @param done {Function} Callback called at serialization end * @returns {XmlToJsonSerializer} Current instance of XmlToJsonSerializer */ XmlToJsonSerializer.prototype.serialize = function serialize(done) { var source = this._input; var before = this._middlewares.before; var after = this._middlewares.after; var context = buildInnerContext(source); var tasks = [utils.injectContext(context)] .concat(before) .concat([utils.try(parseContextSource)]) .concat(after); async.waterfall(tasks, function (err, result) { done(err, result); }); return this; }; /** * Build the internal context object. * * @private * @param source {String} Source string to be parsed * @returns {Object} Context object with source/result property */ function buildInnerContext(source) { return { source: source, result: {} }; } /** * Runs the internal parser and try to parse context.source string * * @private * @param context {Object} Context with source property * @param asyncDone {Function} Called on finish */ function parseContextSource(context, asyncDone) { var source = context.source; try { context.result = parser.parse(source); asyncDone(null, context); } catch (err) { asyncDone(err, context); } } /** * Builds function which maps properties from source object to target object. * * The returned function maps properties from source to target object. * The default source and target objects is context.result. * * Options.target(context, sourcePath, targetPath, options) * If you provide options.target as a function then the target object will * be that object which you have to return when options.target() is called. * * Options.source(ontext, sourcePath, targetPath, options) * If you provide options.source as a function it behaves the same as options.target * function but for source object. * * Options.projection(source, sourcePath, targetPath, target) * If you provide options.projection as a function then you have to * organize the mapping process by your own. * That means this function is responsible to map properties from source to target. * * Options.overwrite === true * If overwrite eq true then context.result (which was the parsed result before) * will be overriden with target. This especially comes to an effekt if you provide * your own target object with options.target(...) function. * * * The overall returning function has the signature: * * function(context, asyncDone){ * } * * Example: * * obj.map('a.b.x', 'a.b.y', { * target: function(context, sourcePath, targetPath, options){ * // do some stuff with own objects * return yourOwnObject; * }, * projection: function(source, sourcePath, targetPath, target){ * target[targetPath] = source[sourcePath]; * }, * overwrite: true * }) * * @private * @param sourcePath {String} Property source path * @param targetPath {String} Property target path * @param options {Object} Provides additional source/target/projection hooks * @returns {Function} Function which maps properties */ function map(sourcePath, targetPath, options) { return function _mapFn(context, asyncDone) { var source = context.result; var target = context.result; // Source object can be provided from outside if (options && typeof options.source === "function") { source = options.source(context, sourcePath, targetPath, options); } // target object can be provided from outside if (options && typeof options.target === "function") { target = options.target(context, sourcePath, targetPath, options); } // Projection can be processed from outside if (options && typeof options.projection === "function") { options.projection(source, sourcePath, targetPath, target); } else { projectProperty(source, sourcePath, targetPath, target); } if (options && typeof options.overwrite === true) { context.result = target; } asyncDone(null, context); }; } /** * Creates a properties mapping function and push to before tasks. * * This runs a mapping function before serialization process. * See also map() function. * * Example: * obj.mapBefore('a.b.x','a.b.y'); * * @param sourcePath {String} Path of the source property * @param targetPath {String} Path of the source property * @param options {Object} See map() * @returns {*|Function|XmlToJsonSerializer} */ XmlToJsonSerializer.prototype.mapBefore = function mapBefore(sourcePath, targetPath, options) { var mapFn = map(sourcePath, targetPath, options); return this.before(mapFn); }; /** * Creates a properties mapping function and push to after tasks. * * This runs a mapping function after serialization process. * See also map() function. * * Example: * obj.mapAfter('a.b.x','a.b.y'); * * @param sourcePath {String} Path of the source property * @param targetPath {String} Path of the source property * @param options - Options, see map() * @returns {XmlToJsonSerializer} Current instance of XmlSerializer */ XmlToJsonSerializer.prototype.mapAfter = function mapAfter(sourcePath, targetPath, options) { var mapFn = map(sourcePath, targetPath, options); return this.after(mapFn); }; /** * Projecting or mapping source value to target property * * @private * @param source {Object} Source object with source value * @param sourcePath {String} Path of the source property * @param targetPath {String} Path of the target property * @param target {object} Target object to attach source value */ function projectProperty(source, sourcePath, targetPath, target) { var sourceValue = lodash.get(source, sourcePath); lodash.set(target, targetPath, sourceValue); }