Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 `<list><elem>1</elem></list> <list><elem>2</elem></list>`. If `false`, it would be marshalled into `<list> <elem>1</elem> <elem>2</elem> </list>`. (**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 `<a><elem>1</elem></a> <b><elem>2</elem></b> <a><elem>3</elem></a>` preserving the original JSON order. By default, if option is not set, it will be marshaled as `<$sequence> <a><elem>1</elem></a> <a><elem>3</elem></a> <b><elem>2</elem></b> </$sequence>`. (**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`)
Expand Down
2 changes: 2 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 <list><elem>1</elem></list> <list><elem>2</elem></list>. If false, marshalls into <list> <elem>1</elem> <elem>2</elem> </list>. 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 <elem>1</elem><elem>2</elem> 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. */
Expand Down
46 changes: 26 additions & 20 deletions src/wsdl/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Comment thread
w666 marked this conversation as resolved.
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(['</', name, '>'].join(''));
} else {
parts.push(['</', appendColon(correctOuterNsPrefix), name, '>'].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(['</', name, '>'].join(''));
} else {
parts.push(['</', appendColon(correctOuterNsPrefix), name, '>'].join(''));
}
}
}
}
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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;
Expand Down
140 changes: 140 additions & 0 deletions test/wsdl-parse-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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');
}
Comment thread
w666 marked this conversation as resolved.

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:
* <acme:getDataResponse xmlns:acme="http://test-soap.com/api/mixedsequence">
* <acme:getDataResult>
* <acme:a>0</acme:a>
* <acme:b>10</acme:b>
* <acme:c>
* <acme:id>1</acme:id>
* <acme:value1>test 1</acme:value1>
* </acme:c>
* <acme:d>
* <acme:id>3</acme:id>
* <acme:value2>test 3</acme:value2>
* </acme:d>
* <acme:c>
* <acme:id>2</acme:id>
* <acme:value1>test 2</acme:value1>
* </acme:c>
* </acme:getDataResult>
* </acme:getDataResponse>
*/
assert.strictEqual(
requestAsXML,
'<acme:getDataResponse xmlns:acme="http://test-soap.com/api/mixedsequence" xmlns="http://test-soap.com/api/mixedsequence"><acme:getDataResult><acme:a>0</acme:a><acme:b>10</acme:b><acme:c><acme:id>1</acme:id><acme:value1>test 1</acme:value1></acme:c><acme:d><acme:id>3</acme:id><acme:value2>test 3</acme:value2></acme:d><acme:c><acme:id>2</acme:id><acme:value1>test 2</acme:value1></acme:c></acme:getDataResult></acme:getDataResponse>',
);

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:
* <acme:getDataResponse xmlns:acme="http://test-soap.com/api/mixedsequence">
* <acme:getDataResult>
* <acme:a>0</acme:a>
* <acme:b>10</acme:b>
* <acme:c>
* <acme:id>1</acme:id>
* <acme:value1>test 1</acme:value1>
* </acme:c>
* <acme:c>
* <acme:id>2</acme:id>
* <acme:value1>test 2</acme:value1>
* </acme:c>
* <acme:d>
* <acme:id>3</acme:id>
* <acme:value2>test 3</acme:value2>
* </acme:d>
* </acme:getDataResult>
* </acme:getDataResponse>
*/
assert.strictEqual(
requestAsXML,
'<acme:getDataResponse xmlns:acme="http://test-soap.com/api/mixedsequence" xmlns="http://test-soap.com/api/mixedsequence"><acme:getDataResult><acme:a>0</acme:a><acme:b>10</acme:b><acme:c><acme:id>1</acme:id><acme:value1>test 1</acme:value1></acme:c><acme:c><acme:id>2</acme:id><acme:value1>test 2</acme:value1></acme:c><acme:d><acme:id>3</acme:id><acme:value2>test 3</acme:value2></acme:d></acme:getDataResult></acme:getDataResponse>',
);

done();
});
});
});
127 changes: 127 additions & 0 deletions test/wsdl/complex/mixed-sequence.wsdl
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
<?xml version="1.0" encoding="utf-8"?>
<wsdl:definitions name="MixedSequenceApi"
xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:tns="http://test-soap.com/api/mixedsequence"
targetNamespace="http://test-soap.com/api/mixedsequence">

<wsdl:types>
<xs:schema targetNamespace="http://test-soap.com/api/mixedsequence" elementFormDefault="qualified">
<xs:simpleType name="id">
<xs:restriction base="xs:string">
<xs:maxLength value="255"/>
</xs:restriction>
</xs:simpleType>

<xs:element name="id" type="tns:id"/>

<xs:element name="getData">
<xs:complexType>
<xs:sequence>
<xs:element ref="tns:id"/>
<xs:element name="index" type="xs:int"/>
</xs:sequence>
</xs:complexType>
</xs:element>

<xs:element name="getDataResponse">
<xs:complexType>
<xs:sequence>
<xs:element name="getDataResult" type="tns:dataList"/>
</xs:sequence>
</xs:complexType>
</xs:element>

<xs:complexType name="dataList">
<xs:sequence>
<xs:element name="a" type="xs:int"/>
<xs:element name="b" type="xs:int"/>
<xs:choice minOccurs="0" maxOccurs="unbounded">
<xs:element name="c" type="tns:Data1"/>
<xs:element name="d" type="tns:Data2"/>
</xs:choice>
</xs:sequence>
</xs:complexType>

<xs:complexType name="AbstractData" abstract="true">
<xs:sequence>
<xs:element ref="tns:id"/>
</xs:sequence>
</xs:complexType>

<xs:complexType name="Data1">
<xs:complexContent>
<xs:extension base="tns:AbstractData">
<xs:sequence>
<xs:element name="value1" type="xs:string" minOccurs="0"/>
</xs:sequence>
</xs:extension>
</xs:complexContent>
</xs:complexType>

<xs:complexType name="Data2">
<xs:complexContent>
<xs:extension base="tns:AbstractData">
<xs:sequence>
<xs:element name="value2" type="xs:string"/>
</xs:sequence>
</xs:extension>
</xs:complexContent>
</xs:complexType>

<xs:element name="customFault">
<xs:complexType>
<xs:choice>
<xs:sequence>
<xs:element name="Error" type="xs:int"/>
<xs:element name="ExceptionInfo" type="xs:string"/>
</xs:sequence>
</xs:choice>
</xs:complexType>
</xs:element>
</xs:schema>
</wsdl:types>

<wsdl:message name="getDataIn">
<wsdl:part name="parameters" element="tns:getData"/>
</wsdl:message>

<wsdl:message name="getDataOut">
<wsdl:part name="parameters" element="tns:getDataResponse"/>
</wsdl:message>

<wsdl:message name="customFault">
<wsdl:part name="customFault" element="tns:customFault"/>
</wsdl:message>

<wsdl:portType name="AcmeService">
<wsdl:operation name="getData">
<wsdl:input message="tns:getDataIn"/>
<wsdl:output message="tns:getDataOut"/>
<wsdl:fault message="tns:customFault" name="customFault"/>
</wsdl:operation>
</wsdl:portType>

<wsdl:binding name="AcmeService" type="tns:AcmeService">
<soap:binding transport="http://schemas.xmlsoap.org/soap/http"/>
<wsdl:operation name="getData">
<soap:operation soapAction="http://test-soap.com/api/mixedsequence#getData" style="document"/>
<wsdl:input>
<soap:body use="literal"/>
</wsdl:input>
<wsdl:output>
<soap:body use="literal"/>
</wsdl:output>
<wsdl:fault name="customFault">
<soap:fault name="customFault" use="literal"/>
</wsdl:fault>
</wsdl:operation>
</wsdl:binding>

<wsdl:service name="AcmeService">
<wsdl:port name="AcmeService" binding="tns:AcmeService">
<soap:address location="http://api.acme.com/TestService.php"/>
</wsdl:port>
</wsdl:service>
</wsdl:definitions>