Skip to content
Open
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
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 array with choice semantics. If array key matches, JSON arrays of the form {$sequence: [{elem: 1}, {elem: 2}]} are marshalled into xml as <elem>1</elem><elem>2</elem>. Default: $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);
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 || '$sequence';

// Allow any request headers to keep passing through
this.options.wsdl_headers = options.wsdl_headers;
Expand Down
147 changes: 147 additions & 0 deletions test/wsdl-parse-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -144,4 +144,151 @@ describe(__filename, function () {
done();
});
});

it('should parse complex wsdls with mixed choice and minOccurs maxOccurs with custom $sequence', function (done) {
open_wsdl(
path.resolve(__dirname, 'wsdl/complex/mixed-sequence.wsdl'),
{
arrayWithChoiceTag: '$arrayChoice',
},
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 on lines +159 to +161

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Use findSchemaObject for getDataResponse lookup.

Line 159 and Line 231 call findSchemaType, but getDataResponse is an element with an anonymous complex type in mixed-sequence.wsdl, so this lookup is incorrect.

💡 Proposed fix
-        if (null === def.findSchemaType('getDataResponse', 'http://test-soap.com/api/mixedsequence')) {
-          return done('Unable to find "getDataResponse" complex type');
+        if (null === def.findSchemaObject('http://test-soap.com/api/mixedsequence', 'getDataResponse')) {
+          return done('Unable to find "getDataResponse" schema object');
         }
@@
-        if (null === def.findSchemaType('getDataResponse', 'http://test-soap.com/api/mixedsequence')) {
-          return done('Unable to find "getDataResponse" complex type');
+        if (null === def.findSchemaObject('http://test-soap.com/api/mixedsequence', 'getDataResponse')) {
+          return done('Unable to find "getDataResponse" schema object');
         }

Also applies to: 231-233

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/wsdl-parse-test.js` around lines 159 - 161, The test is using
def.findSchemaType to look up the element "getDataResponse" but that element has
an anonymous complex type, so change both lookups to use
def.findSchemaObject('getDataResponse',
'http://test-soap.com/api/mixedsequence') instead of def.findSchemaType; update
the two occurrences around the current getDataResponse assertions (the call at
the earlier block and the similar call at the later block around lines 231-233)
so the test queries the schema object (element) rather than searching for a
named type.


var requestBody = {
getDataResult: {
a: 0,
b: 10,
$arrayChoice: [
{
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 default $sequence', 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,
$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();
},
);
});
});
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>