@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
JavaScript
;
/**
* 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);
}