From ba5b064b3b4c563a129353068abfc5e235e32983 Mon Sep 17 00:00:00 2001 From: David Cheung Date: Tue, 5 Jan 2016 15:44:26 -0500 Subject: [PATCH] Customize XML root element in remoteMethod - Add option to customize the XML rootElement tag (default: "response") - Add flag to disable declaration from response (default: declaration is present) - Bugfix changing behavior: `string` results from remote methods are now quoted to avoid json2xmlparser throwing syntax error and returning 500 instead of a regular response body. User can set the returned XML root element as follows: "returns": { "xml": { "wrapperElement": "Parent" } } User can disable XML declaration as follows: "returns": { "xml": { "declaration": false } } --- lib/http-context.js | 20 +++++--- test/rest.test.js | 115 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 129 insertions(+), 6 deletions(-) diff --git a/lib/http-context.js b/lib/http-context.js index 27ce93ee..9257a9a0 100644 --- a/lib/http-context.js +++ b/lib/http-context.js @@ -433,19 +433,26 @@ function toJSON(input) { } } -function toXML(input) { +function toXML(input, options) { var xml; + var xmlDefaultOptions = { declaration : true }; + var xmlOptions = util._extend(xmlDefaultOptions, options); if (input && typeof input.toXML === 'function') { xml = input.toXML(); } else { - if (input) { + if (typeof input == 'string') { + // this is a hack to handle js2xmlparser throws syntax error on string + // wraps string from "a string" to "\"a string\"" + input = JSON.stringify(input); + } else if (typeof input == 'object') { // Trigger toJSON() conversions input = toJSON(input); } if (Array.isArray(input)) { input = { result: input }; } - xml = js2xmlparser('response', input, { + xml = js2xmlparser(xmlOptions.wrapperElement || 'response', input, { + declaration : { include : xmlOptions.declaration }, prettyPrinting: { indentString: ' ' }, @@ -501,13 +508,14 @@ function sendBodyJsonp(res, data) { res.jsonp(data); } -function sendBodyXml(res, data) { +function sendBodyXml(res, data, method) { if (data === null) { res.header('Content-Length', '7'); res.send(''); } else if (data) { try { - var xml = toXML(data); + var xmlOptions = method.returns[0].xml || {}; + var xml = toXML(data, xmlOptions); res.send(xml); } catch (e) { res.status(500).send(e + '\n' + data); @@ -629,7 +637,7 @@ HttpContext.prototype.done = function(cb) { res.header('Content-Type', operationResults.contentType); } if (dataExists) { - operationResults.sendBody(res, data); + operationResults.sendBody(res, data, method); } else { if (res.statusCode === undefined || res.statusCode === 200) { res.statusCode = 204; diff --git a/test/rest.test.js b/test/rest.test.js index d73986d6..31f83a81 100644 --- a/test/rest.test.js +++ b/test/rest.test.js @@ -1359,6 +1359,121 @@ describe('strong-remoting-rest', function() { }); }); + it('should allow customized xml root element', function(done) { + var method = givenSharedStaticMethod( + function bar(cb) { + cb(null, {a: 1, b: 2}); + }, + { + returns: { + arg: 'data', type: 'object', root: true, + xml: { wrapperElement: 'foo'} + }, + http: { path: '/' } + } + ); + request(app).get(method.classUrl) + .set('Accept', 'application/xml') + .set('Content-Type', 'application/json') + .send() + .expect('Content-Type', /xml/) + .expect(200, function(err, res) { + expect(res.text).to.equal( + '\n' + + '\n ' + + '1\n ' + + '2\n' + + ''); + done(err, res); + }); + }); + + it('should allow xml declaration to be disabled', function(done) { + var method = givenSharedStaticMethod( + function bar(cb) { + cb(null, {a: 1, b: 2}); + }, + { + returns: { + arg: 'data', type: 'object', root: true, + xml: { declaration : false } + }, + http: { path: '/' } + } + ); + request(app).get(method.classUrl) + .set('Accept', 'application/xml') + .set('Content-Type', 'application/json') + .send() + .expect('Content-Type', /xml/) + .expect(200, function(err, res) { + expect(res.text).to.equal( + '\n ' + + '1\n ' + + '2\n' + + ''); + done(err, res); + }); + }); + + it('should allow string results to output as xml', function(done) { + var method = givenSharedStaticMethod( + function bar(cb) { + var stringResult = 'a quick brown fox jumps over the lazy dog'; + cb(null, stringResult); + }, + { + returns: { + root: true, + xml: { wrapperElement : 'text' } + }, + http: { path: '/' } + } + ); + request(app).get(method.classUrl) + .set('Accept', 'application/xml') + .set('Content-Type', 'application/json') + .send() + .expect('Content-Type', /xml/) + .expect(200, function(err, res) { + expect(res.text).to.equal( + '\n' + + 'a quick brown fox jumps over the lazy dog' + + ''); + done(err, res); + }); + }); + + it('should handle UTF-8 & special & reserved characters', function(done) { + var method = givenSharedStaticMethod( + function bar(cb) { + var stringA = 'foo\xC1\xE1\u0102\u03A9asd><=$~!@#$%^&*()-_=+/.,;\'"[]{}?'; + cb(null, {a: stringA}); + }, + { + returns: { + arg: 'data', type: 'object', root: true, + xml: { wrapperElement: false } + }, + http: { path: '/' } + } + ); + request(app).get(method.classUrl) + .set('Accept', 'application/xml') + .set('Content-Type', 'application/json') + .send() + .expect('Content-Type', /xml/) + .expect(200, function(err, res) { + expect(res).to.be.utf8; + expect(res.text).to.equal( + '\n' + + '\n ' + + 'fooÁáĂΩasd><=$~!@#$%^&*()-_=+/.,;'"[]{}?\n' + + ''); + done(); + }); + }); + it('should produce xml from json objects with toXML()', function(done) { var method = givenSharedStaticMethod( function bar(a, cb) {