diff --git a/Readme.md b/Readme.md index fa06680c1..1baa06e31 100644 --- a/Readme.md +++ b/Readme.md @@ -137,6 +137,7 @@ Creates a new SOAP client from a WSDL URL. Also supports a local filesystem path - `overridePromiseSuffix` (_string_): Override the default method name suffix of WSDL operations for Promise-based methods. If any WSDL operation name ends with `Async', you must use this option. (**Default:** `Async`) - `normalizeNames` (_boolean_): Replace non-identifier characters (`[^a-z$_0-9]`) with `_` in WSDL operation names. Note: Clients using WSDLs with two operations like `soap:method` and `soap-method` will be overwritten. In this case, you must use bracket notation instead (`client['soap:method']()`). - `namespaceArrayElements` (_boolean_): Support non-standard array semantics. JSON arrays of the form `{list: [{elem: 1}, {elem: 2}]}` will be marshalled into XML as `1 2`. If `false`, it would be marshalled into ` 1 2 `. (**Default:** `true`) + - `arrayWithChoiceTag` (_string_): Support for mixed value sequence with choice semantics instead of ordered by type one. If `arrayWithChoiceTag` option is set and sequence key matches the option value, JSON arrays of the form `{$sequence: [{a: {elem: 1}}, {b: {elem: 2}}, {a: {elem: 3}}]}` (where `$sequence` is example value for the option) are marshaled into xml as `1 2 3` preserving the original JSON order. By default, if option is not set, it will be marshaled as `<$sequence> 1 3 2 `. (**Default:** Disabled if option is not set) - `stream` (_boolean_): Use streams to parse the XML SOAP responses. (**Default:** `false`) - `returnSaxStream` (_boolean_): Return the SAX stream, transferring responsibility of parsing XML to the end user. Only valid when the _stream_ option is set to `true`. (**Default:** `false`) - `parseReponseAttachments` (_boolean_): Treat response as multipart/related response with MTOM attachment. Reach attachments on the `lastResponseAttachments` property of SoapClient. (**Default:** `false`) diff --git a/src/types.ts b/src/types.ts index 896a0bf08..65e3423d9 100644 --- a/src/types.ts +++ b/src/types.ts @@ -99,6 +99,8 @@ export interface IWsdlBaseOptions { preserveWhitespace?: boolean; /** provides support for nonstandard array semantics. If true, JSON arrays of the form {list: [{elem: 1}, {elem: 2}]} are marshalled into xml as 1 2. If false, marshalls into 1 2 . Default: true. */ namespaceArrayElements?: boolean; + /** provides support for sequence with choice semantics. If sequence key matches, JSON arrays of the form {$sequence: [{elem: 1}, {elem: 2}]} are marshalled into xml as 12 where $sequence is example value for the option. Disabled if option is not set. Example value: $sequence. */ + arrayWithChoiceTag?: string; useEmptyTag?: boolean; strict?: boolean; /** custom HTTP headers to be sent on WSDL requests. */ diff --git a/src/wsdl/index.ts b/src/wsdl/index.ts index 68fc61705..d705a2355 100644 --- a/src/wsdl/index.ts +++ b/src/wsdl/index.ts @@ -750,31 +750,36 @@ export class WSDL { for (i = 0, n = obj.length; i < n; i++) { const item = obj[i]; - const arrayAttr = this.processAttributes(item, nsContext); + const isArrayWithChoiceTagContainer = name === this.options.arrayWithChoiceTag; + const arrayAttr = isArrayWithChoiceTagContainer ? '' : this.processAttributes(item, nsContext); const correctOuterNsPrefix = nonSubNameSpace || parentNsPrefix || ns; // using the parent namespace prefix if given const body = this.objectToXML(item, name, nsPrefix, nsURI, false, null, schemaObject, nsContext); - let openingTagParts = ['<', name, arrayAttr, xmlnsAttrib]; - if (!emptyNonSubNameSpaceForArray) { - openingTagParts = ['<', appendColon(correctOuterNsPrefix), name, arrayAttr, xmlnsAttrib]; - } - - if (body === '' && this.options.useEmptyTag) { - // Use empty (self-closing) tags if no contents - openingTagParts.push(' />'); - parts.push(openingTagParts.join('')); + if (isArrayWithChoiceTagContainer) { + parts.push(body); } else { - openingTagParts.push('>'); - if (this.options.namespaceArrayElements || i === 0) { - parts.push(openingTagParts.join('')); + let openingTagParts = ['<', name, arrayAttr, xmlnsAttrib]; + if (!emptyNonSubNameSpaceForArray) { + openingTagParts = ['<', appendColon(correctOuterNsPrefix), name, arrayAttr, xmlnsAttrib]; } - parts.push(body); - if (this.options.namespaceArrayElements || i === n - 1) { - if (emptyNonSubNameSpaceForArray) { - parts.push([''].join('')); - } else { - parts.push([''].join('')); + + if (body === '' && this.options.useEmptyTag) { + // Use empty (self-closing) tags if no contents + openingTagParts.push(' />'); + parts.push(openingTagParts.join('')); + } else { + openingTagParts.push('>'); + if (this.options.namespaceArrayElements || i === 0) { + parts.push(openingTagParts.join('')); + } + parts.push(body); + if (this.options.namespaceArrayElements || i === n - 1) { + if (emptyNonSubNameSpaceForArray) { + parts.push([''].join('')); + } else { + parts.push([''].join('')); + } } } } @@ -939,7 +944,7 @@ export class WSDL { } } - value = this.objectToXML(child, name, nsPrefix, nsURI, false, null, null, nsContext); + value = this.objectToXML(child, name, nsPrefix, nsURI, false, null, name === this.options.arrayWithChoiceTag ? schemaObject : null, nsContext); } } else { value = this.objectToXML(child, name, nsPrefix, nsURI, false, null, null, nsContext); @@ -1178,6 +1183,7 @@ export class WSDL { } else { this.options.namespaceArrayElements = true; } + this.options.arrayWithChoiceTag = options.arrayWithChoiceTag; // Allow any request headers to keep passing through this.options.wsdl_headers = options.wsdl_headers; diff --git a/test/wsdl-parse-test.js b/test/wsdl-parse-test.js index 87709f26f..3ce286850 100644 --- a/test/wsdl-parse-test.js +++ b/test/wsdl-parse-test.js @@ -144,4 +144,144 @@ describe(__filename, function () { done(); }); }); + + it('should parse complex wsdls with mixed choice and minOccurs maxOccurs with specified arrayWithChoiceTag tag key', function (done) { + open_wsdl( + path.resolve(__dirname, 'wsdl/complex/mixed-sequence.wsdl'), + { + arrayWithChoiceTag: '$sequence', + }, + function (err, def) { + if (err) { + return done(err); + } + + if (null === def.findSchemaType('getDataResponse', 'http://test-soap.com/api/mixedsequence')) { + return done('Unable to find "getDataResponse" complex type'); + } + + var requestBody = { + getDataResult: { + a: 0, + b: 10, + $sequence: [ + { + c: { + id: '1', + value1: 'test 1', + }, + }, + { + d: { + id: '3', + value2: 'test 3', + }, + }, + { + c: { + id: '2', + value1: 'test 2', + }, + }, + ], + }, + }; + + var requestAsXML = def.objectToDocumentXML('getDataResponse', requestBody, 'acme', 'http://test-soap.com/api/mixedsequence', 'getDataResponse'); + + /** + * Expected XML: + * + * + * 0 + * 10 + * + * 1 + * test 1 + * + * + * 3 + * test 3 + * + * + * 2 + * test 2 + * + * + * + */ + assert.strictEqual( + requestAsXML, + '0101test 13test 32test 2', + ); + + done(); + }, + ); + }); + + it('should parse complex wsdls with mixed choice and minOccurs maxOccurs with omitted arrayWithChoiceTag tag key', function (done) { + open_wsdl(path.resolve(__dirname, 'wsdl/complex/mixed-sequence.wsdl'), function (err, def) { + if (err) { + return done(err); + } + + if (null === def.findSchemaType('getDataResponse', 'http://test-soap.com/api/mixedsequence')) { + return done('Unable to find "getDataResponse" complex type'); + } + + var requestBody = { + getDataResult: { + a: 0, + b: 10, + c: [ + { + id: '1', + value1: 'test 1', + }, + { + id: '2', + value1: 'test 2', + }, + ], + d: [ + { + id: '3', + value2: 'test 3', + }, + ], + }, + }; + + var requestAsXML = def.objectToDocumentXML('getDataResponse', requestBody, 'acme', 'http://test-soap.com/api/mixedsequence', 'getDataResponse'); + + /** + * Expected XML: + * + * + * 0 + * 10 + * + * 1 + * test 1 + * + * + * 2 + * test 2 + * + * + * 3 + * test 3 + * + * + * + */ + assert.strictEqual( + requestAsXML, + '0101test 12test 23test 3', + ); + + done(); + }); + }); }); diff --git a/test/wsdl/complex/mixed-sequence.wsdl b/test/wsdl/complex/mixed-sequence.wsdl new file mode 100644 index 000000000..bcae576d8 --- /dev/null +++ b/test/wsdl/complex/mixed-sequence.wsdl @@ -0,0 +1,127 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +