ti-soap
Version:
A (superagent-based) simple SOAP client for Titanium SDK
1,685 lines (1,459 loc) • 48.8 kB
JavaScript
/*
* Copyright (c) 2011 Vinay Pulim <vinay@milewise.com>
* MIT Licensed
*
*/
/*jshint proto:true*/
;
var sax = require('sax');
var inherits = require('util').inherits;
var http = require('./http');
var fs = require('fs');
var url = require('url');
var path = require('path');
var assert = require('assert').ok;
var stripBom = require('strip-bom');
var Primitives = {
string: 1,
boolean: 1,
decimal: 1,
float: 1,
double: 1,
anyType: 1,
byte: 1,
int: 1,
long: 1,
short: 1,
unsignedByte: 1,
unsignedInt: 1,
unsignedLong: 1,
unsignedShort: 1,
duration: 0,
dateTime: 0,
time: 0,
date: 0,
gYearMonth: 0,
gYear: 0,
gMonthDay: 0,
gDay: 0,
gMonth: 0,
hexBinary: 0,
base64Binary: 0,
anyURI: 0,
QName: 0,
NOTATION: 0
};
function splitNSName(nsName) {
var i = typeof nsName === 'string' ? nsName.indexOf(':') : -1;
return i < 0 ? {namespace: 'xmlns', name: nsName} : {namespace: nsName.substring(0, i), name: nsName.substring(i + 1)};
}
function xmlEscape(obj) {
if (typeof (obj) === 'string') {
return obj
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
}
return obj;
}
var trimLeft = /^[\s\xA0]+/;
var trimRight = /[\s\xA0]+$/;
function trim(text) {
return text.replace(trimLeft, '').replace(trimRight, '');
}
function extend(base, obj) {
for (var key in obj) {
if (!obj.hasOwnProperty(key)) {
base[key] = obj[key];
}
}
return base;
}
function findKey(obj, val) {
for (var n in obj)
if (obj[n] === val)
return n;
}
var Element = function(nsName, attrs, options) {
var parts = splitNSName(nsName);
this.nsName = nsName;
this.namespace = parts.namespace;
this.name = parts.name;
this.children = [];
this.xmlns = {};
this._initializeOptions(options);
for (var key in attrs) {
var match = /^xmlns:?(.*)$/.exec(key);
if (match) {
this.xmlns[match[1] ? match[1] : 'xmlns'] = attrs[key];
}
else {
if(key === 'value') {
this[this.valueKey] = attrs[key];
} else {
this['$' + key] = attrs[key];
}
}
}
};
Element.prototype._initializeOptions = function (options) {
if(options) {
this.valueKey = options.valueKey || '$value';
this.ignoredNamespaces = options.ignoredNamespaces || [];
} else {
this.valueKey = '$value';
this.ignoredNamespaces = [];
}
};
Element.prototype.deleteFixedAttrs = function() {
this.children && this.children.length === 0 && delete this.children;
this.xmlns && Object.keys(this.xmlns).length === 0 && delete this.xmlns;
delete this.nsName;
delete this.namespace;
delete this.name;
};
Element.prototype.allowedChildren = [];
Element.prototype.startElement = function(stack, nsName, attrs, options) {
if (!this.allowedChildren)
return;
var ChildClass = this.allowedChildren[splitNSName(nsName).name],
element = null;
if (ChildClass) {
stack.push(new ChildClass(nsName, attrs, options));
}
else {
this.unexpected(nsName);
}
};
Element.prototype.endElement = function(stack, nsName) {
if (this.nsName === nsName) {
if (stack.length < 2)
return;
var parent = stack[stack.length - 2];
if (this !== stack[0]) {
extend(stack[0].xmlns, this.xmlns);
// delete this.xmlns;
parent.children.push(this);
parent.addChild(this);
}
stack.pop();
}
};
Element.prototype.addChild = function(child) {
return;
};
Element.prototype.unexpected = function(name) {
throw new Error('Found unexpected element (' + name + ') inside ' + this.nsName);
};
Element.prototype.description = function(definitions) {
return this.$name || this.name;
};
Element.prototype.init = function() {
};
Element.createSubClass = function() {
var root = this;
var subElement = function() {
root.apply(this, arguments);
this.init();
};
// inherits(subElement, root);
subElement.prototype.__proto__ = root.prototype;
return subElement;
};
var ElementElement = Element.createSubClass();
var AnyElement = Element.createSubClass();
var InputElement = Element.createSubClass();
var OutputElement = Element.createSubClass();
var SimpleTypeElement = Element.createSubClass();
var RestrictionElement = Element.createSubClass();
var ExtensionElement = Element.createSubClass();
var ChoiceElement = Element.createSubClass();
var EnumerationElement = Element.createSubClass();
var ComplexTypeElement = Element.createSubClass();
var ComplexContentElement = Element.createSubClass();
var SimpleContentElement = Element.createSubClass();
var SequenceElement = Element.createSubClass();
var AllElement = Element.createSubClass();
var MessageElement = Element.createSubClass();
var DocumentationElement = Element.createSubClass();
var SchemaElement = Element.createSubClass();
var TypesElement = Element.createSubClass();
var OperationElement = Element.createSubClass();
var PortTypeElement = Element.createSubClass();
var BindingElement = Element.createSubClass();
var PortElement = Element.createSubClass();
var ServiceElement = Element.createSubClass();
var DefinitionsElement = Element.createSubClass();
var ElementTypeMap = {
types: [TypesElement, 'schema documentation'],
schema: [SchemaElement, 'element complexType simpleType include import'],
element: [ElementElement, 'annotation complexType'],
any: [AnyElement, ''],
simpleType: [SimpleTypeElement, 'restriction'],
restriction: [RestrictionElement, 'enumeration choice sequence'],
extension: [ExtensionElement, 'sequence choice'],
choice: [ChoiceElement, 'element choice'],
// group: [GroupElement, 'element group'],
enumeration: [EnumerationElement, ''],
complexType: [ComplexTypeElement, 'annotation sequence all complexContent simpleContent choice'],
complexContent: [ComplexContentElement, 'extension'],
simpleContent: [SimpleContentElement, 'extension'],
sequence: [SequenceElement, 'element choice any'],
all: [AllElement, 'element choice'],
service: [ServiceElement, 'port documentation'],
port: [PortElement, 'address documentation'],
binding: [BindingElement, '_binding SecuritySpec operation documentation'],
portType: [PortTypeElement, 'operation documentation'],
message: [MessageElement, 'part documentation'],
operation: [OperationElement, 'documentation input output fault _operation'],
input: [InputElement, 'body SecuritySpecRef documentation header'],
output: [OutputElement, 'body SecuritySpecRef documentation header'],
fault: [Element, '_fault documentation'],
definitions: [DefinitionsElement, 'types message portType binding service import documentation'],
documentation: [DocumentationElement, '']
};
function mapElementTypes(types) {
var rtn = {};
types = types.split(' ');
types.forEach(function(type) {
rtn[type.replace(/^_/, '')] = (ElementTypeMap[type] || [Element]) [0];
});
return rtn;
}
for (var n in ElementTypeMap) {
var v = ElementTypeMap[n];
v[0].prototype.allowedChildren = mapElementTypes(v[1]);
}
MessageElement.prototype.init = function() {
this.element = null;
this.parts = null;
};
SchemaElement.prototype.init = function() {
this.complexTypes = {};
this.types = {};
this.elements = {};
this.includes = [];
};
TypesElement.prototype.init = function() {
this.schemas = {};
};
OperationElement.prototype.init = function() {
this.input = null;
this.output = null;
this.inputSoap = null;
this.outputSoap = null;
this.style = '';
this.soapAction = '';
};
PortTypeElement.prototype.init = function() {
this.methods = {};
};
BindingElement.prototype.init = function() {
this.transport = '';
this.style = '';
this.methods = {};
};
PortElement.prototype.init = function() {
this.location = null;
};
ServiceElement.prototype.init = function() {
this.ports = {};
};
DefinitionsElement.prototype.init = function() {
if (this.name !== 'definitions')this.unexpected(this.nsName);
this.messages = {};
this.portTypes = {};
this.bindings = {};
this.services = {};
this.schemas = {};
};
DocumentationElement.prototype.init = function() {
};
SchemaElement.prototype.addChild = function(child) {
if (child.$name in Primitives)
return;
if (child.name === 'include' || child.name === 'import') {
var location = child.$schemaLocation || child.$location;
if (location) {
this.includes.push({
namespace: child.$namespace || child.$targetNamespace || this.$targetNamespace,
location: location
});
}
}
else if (child.name === 'complexType') {
this.complexTypes[child.$name] = child;
}
else if (child.name === 'element') {
this.elements[child.$name] = child;
}
else if (child.$name) {
this.types[child.$name] = child;
}
this.children.pop();
// child.deleteFixedAttrs();
};
//fix#325
TypesElement.prototype.addChild = function (child) {
assert(child instanceof SchemaElement);
var targetNamespace = child.$targetNamespace;
if(!targetNamespace) {
if(child.includes && (child.includes instanceof Array) && child.includes.length > 0) {
targetNamespace = child.includes[0].namespace;
}
}
if(!this.schemas.hasOwnProperty(targetNamespace)) {
this.schemas[targetNamespace] = child;
} else {
console.error('Target-Namespace "'+ targetNamespace +'" already in use by another Schema!');
}
};
InputElement.prototype.addChild = function(child) {
if (child.name === 'body') {
this.use = child.$use;
if (this.use === 'encoded') {
this.encodingStyle = child.$encodingStyle;
}
this.children.pop();
}
};
OutputElement.prototype.addChild = function(child) {
if (child.name === 'body') {
this.use = child.$use;
if (this.use === 'encoded') {
this.encodingStyle = child.$encodingStyle;
}
this.children.pop();
}
};
OperationElement.prototype.addChild = function(child) {
if (child.name === 'operation') {
this.soapAction = child.$soapAction || '';
this.style = child.$style || '';
this.children.pop();
}
};
BindingElement.prototype.addChild = function(child) {
if (child.name === 'binding') {
this.transport = child.$transport;
this.style = child.$style;
this.children.pop();
}
};
PortElement.prototype.addChild = function(child) {
if (child.name === 'address' && typeof (child.$location) !== 'undefined') {
this.location = child.$location;
}
};
DefinitionsElement.prototype.addChild = function(child) {
var self = this;
if (child instanceof TypesElement) {
self.schemas = child.schemas;
}
else if (child instanceof MessageElement) {
self.messages[child.$name] = child;
}
else if (child.name === 'import') {
self.schemas[child.$namespace] = new SchemaElement(child.$namespace, {});
self.schemas[child.$namespace].addChild(child);
}
else if (child instanceof PortTypeElement) {
self.portTypes[child.$name] = child;
}
else if (child instanceof BindingElement) {
if (child.transport === 'http://schemas.xmlsoap.org/soap/http' ||
child.transport === 'http://www.w3.org/2003/05/soap/bindings/HTTP/')
self.bindings[child.$name] = child;
}
else if (child instanceof ServiceElement) {
self.services[child.$name] = child;
}
else if (child instanceof DocumentationElement) {
}
this.children.pop();
};
MessageElement.prototype.postProcess = function(definitions) {
var part = null;
var child;
var children = this.children || [];
var ns;
var nsName;
var i;
var type;
for (i in children) {
if ((child = children[i]).name === 'part') {
part = child;
break;
}
}
if (!part) {
return;
}
if (part.$element) {
var lookupTypes = [],
elementChildren ;
delete this.parts;
nsName = splitNSName(part.$element);
ns = nsName.namespace;
var schema = definitions.schemas[definitions.xmlns[ns]];
this.element = schema.elements[nsName.name];
this.element.targetNSAlias = ns;
this.element.targetNamespace = definitions.xmlns[ns];
// set the optional $lookupType to be used within `client#_invoke()` when
// calling `wsdl#objectToDocumentXML()
this.element.$lookupType = part.$name;
elementChildren = this.element.children;
// get all nested lookup types (only complex types are followed)
if (elementChildren.length > 0) {
for (i = 0; i < elementChildren.length; i++) {
lookupTypes.push(this._getNestedLookupTypeString(elementChildren[i]));
}
}
// if nested lookup types where found, prepare them for furter usage
if (lookupTypes.length > 0) {
lookupTypes = lookupTypes.
join('_').
split('_').
filter(function removeEmptyLookupTypes (type) {
return type !== '^';
});
var schemaXmlns = definitions.schemas[this.element.targetNamespace].xmlns;
for (i = 0; i < lookupTypes.length; i++) {
lookupTypes[i] = this._createLookupTypeObject(lookupTypes[i], schemaXmlns);
}
}
this.element.$lookupTypes = lookupTypes;
if (this.element.$type) {
type = splitNSName(this.element.$type);
var typeNs = schema.xmlns && schema.xmlns[type.namespace] || definitions.xmlns[type.namespace];
if (typeNs) {
if (type.name in Primitives) {
// this.element = this.element.$type;
}
else {
// first check local mapping of ns alias to namespace
schema = definitions.schemas[typeNs];
var ctype = schema.complexTypes[type.name] || schema.types[type.name] || schema.elements[type.name];
if (ctype) {
this.parts = ctype.description(definitions, schema.xmlns);
}
}
}
}
else {
var method = this.element.description(definitions, schema.xmlns);
this.parts = method[nsName.name];
}
this.children.splice(0, 1);
} else {
// rpc encoding
this.parts = {};
delete this.element;
for (i = 0; part = this.children[i]; i++) {
assert(part.name === 'part', 'Expected part element');
nsName = splitNSName(part.$type);
ns = definitions.xmlns[nsName.namespace];
type = nsName.name;
var schemaDefinition = definitions.schemas[ns];
if (typeof schemaDefinition !== 'undefined') {
this.parts[part.$name] = definitions.schemas[ns].types[type] || definitions.schemas[ns].complexTypes[type];
} else {
this.parts[part.$name] = part.$type;
}
this.parts[part.$name].namespace = nsName.namespace;
this.parts[part.$name].xmlns = ns;
this.children.splice(i--, 1);
}
}
this.deleteFixedAttrs();
};
/**
* Takes a given namespaced String(for example: 'alias:property') and creates a lookupType
* object for further use in as first (lookup) `parameterTypeObj` within the `objectToXML`
* method and provides an entry point for the already existing code in `findChildParameterObject`.
*
* @method _createLookupTypeObject
* @param {String} nsString The NS String (for example "alias:type").
* @param {Object} xmlns The fully parsed `wsdl` definitions object (including all schemas).
* @returns {Object}
* @private
*/
MessageElement.prototype._createLookupTypeObject = function (nsString, xmlns) {
var splittedNSString = splitNSName(nsString),
nsAlias = splittedNSString.namespace,
splittedName = splittedNSString.name.split('#'),
type = splittedName[0],
name = splittedName[1],
lookupTypeObj = {};
lookupTypeObj.$namespace = xmlns[nsAlias];
lookupTypeObj.$type = nsAlias + ':' + type;
lookupTypeObj.$name = name;
return lookupTypeObj;
};
/**
* Iterates through the element and every nested child to find any defined `$type`
* property and returns it in a underscore ('_') separated String (using '^' as default
* value if no `$type` property was found).
*
* @method _getNestedLookupTypeString
* @param {Object} element The element which (probably) contains nested `$type` values.
* @returns {String}
* @private
*/
MessageElement.prototype._getNestedLookupTypeString = function (element) {
var resolvedType = '^',
excluded = this.ignoredNamespaces.concat('xs'); // do not process $type values wich start with
if (element.hasOwnProperty('$type') && typeof element.$type === 'string') {
if (excluded.indexOf(element.$type.split(':')[0]) === -1) {
resolvedType += ('_' + element.$type + '#' + element.$name);
}
}
if (element.children.length > 0) {
var self = this;
element.children.forEach(function (child) {
var resolvedChildType = self._getNestedLookupTypeString(child).replace(/\^_/, '');
if (resolvedChildType && typeof resolvedChildType === 'string') {
resolvedType += ('_' + resolvedChildType);
}
});
}
return resolvedType;
};
OperationElement.prototype.postProcess = function(definitions, tag) {
var children = this.children;
for (var i = 0, child; child = children[i]; i++) {
if (child.name !== 'input' && child.name !== 'output')
continue;
if (tag === 'binding') {
this[child.name] = child;
children.splice(i--, 1);
continue;
}
var messageName = splitNSName(child.$message).name;
var message = definitions.messages[messageName];
message.postProcess(definitions);
if (message.element) {
definitions.messages[message.element.$name] = message;
this[child.name] = message.element;
}
else {
this[child.name] = message;
}
children.splice(i--, 1);
}
this.deleteFixedAttrs();
};
PortTypeElement.prototype.postProcess = function(definitions) {
var children = this.children;
if (typeof children === 'undefined')
return;
for (var i = 0, child; child = children[i]; i++) {
if (child.name !== 'operation')
continue;
child.postProcess(definitions, 'portType');
this.methods[child.$name] = child;
children.splice(i--, 1);
}
delete this.$name;
this.deleteFixedAttrs();
};
BindingElement.prototype.postProcess = function(definitions) {
var type = splitNSName(this.$type).name,
portType = definitions.portTypes[type],
style = this.style,
children = this.children;
if (portType){
portType.postProcess(definitions);
this.methods = portType.methods;
for (var i = 0, child; child = children[i]; i++) {
if (child.name !== 'operation')
continue;
child.postProcess(definitions, 'binding');
children.splice(i--, 1);
child.style || (child.style = style);
var method = this.methods[child.$name];
if (method) {
method.style = child.style;
method.soapAction = child.soapAction;
method.inputSoap = child.input || null;
method.outputSoap = child.output || null;
method.inputSoap && method.inputSoap.deleteFixedAttrs();
method.outputSoap && method.outputSoap.deleteFixedAttrs();
}
}
}
delete this.$name;
delete this.$type;
this.deleteFixedAttrs();
};
ServiceElement.prototype.postProcess = function(definitions) {
var children = this.children,
bindings = definitions.bindings;
for (var i = 0, child; child = children[i]; i++) {
if (child.name !== 'port')
continue;
var bindingName = splitNSName(child.$binding).name;
var binding = bindings[bindingName];
if (binding) {
binding.postProcess(definitions);
this.ports[child.$name] = {
location: child.location,
binding: binding
};
children.splice(i--, 1);
}
}
delete this.$name;
this.deleteFixedAttrs();
};
SimpleTypeElement.prototype.description = function(definitions) {
var children = this.children;
for (var i = 0, child; child = children[i]; i++) {
if (child instanceof RestrictionElement)
return this.$name + "|" + child.description();
}
return {};
};
RestrictionElement.prototype.description = function(definitions, xmlns) {
var children = this.children;
var desc;
for (var i=0, child; child=children[i]; i++) {
if (child instanceof SequenceElement ||
child instanceof ChoiceElement) {
desc = child.description(definitions);
break;
}
}
if (desc && this.$base) {
var type = splitNSName(this.$base),
typeName = type.name,
ns = xmlns && xmlns[type.namespace] || definitions.xmlns[type.namespace],
schema = definitions.schemas[ns],
typeElement = schema && ( schema.complexTypes[typeName] || schema.types[typeName] || schema.elements[typeName] );
desc.getBase = function() {
return typeElement.description(definitions, xmlns);
};
return desc;
}
// then simple element
var base = this.$base ? this.$base + "|" : "";
return base + this.children.map(function(child) {
return child.description();
}).join(",");
};
ExtensionElement.prototype.description = function(definitions, xmlns) {
var children = this.children;
var desc = {};
for (var i=0, child; child=children[i]; i++) {
if (child instanceof SequenceElement ||
child instanceof ChoiceElement) {
desc = child.description(definitions);
}
}
if (this.$base) {
var type = splitNSName(this.$base),
typeName = type.name,
ns = xmlns && xmlns[type.namespace] || definitions.xmlns[type.namespace],
schema = definitions.schemas[ns];
if (typeName in Primitives) {
return this.$base;
}
else {
var typeElement = schema && ( schema.complexTypes[typeName] ||
schema.types[typeName] || schema.elements[typeName] );
var base = typeElement.description(definitions, xmlns);
extend(desc, base);
}
}
return desc;
};
EnumerationElement.prototype.description = function() {
return this[this.valueKey];
};
ComplexTypeElement.prototype.description = function(definitions, xmlns) {
var children = this.children || [];
for (var i=0, child; child=children[i]; i++) {
if (child instanceof ChoiceElement ||
child instanceof SequenceElement ||
child instanceof AllElement ||
child instanceof SimpleContentElement ||
child instanceof ComplexContentElement) {
return child.description(definitions, xmlns);
}
}
return {};
};
ComplexContentElement.prototype.description = function(definitions, xmlns) {
var children = this.children;
for (var i = 0, child; child = children[i]; i++) {
if (child instanceof ExtensionElement) {
return child.description(definitions, xmlns);
}
}
return {};
};
SimpleContentElement.prototype.description = function(definitions, xmlns) {
var children = this.children;
for (var i = 0, child; child = children[i]; i++) {
if (child instanceof ExtensionElement) {
return child.description(definitions, xmlns);
}
}
return {};
};
ElementElement.prototype.description = function(definitions, xmlns) {
var element = {},
name = this.$name;
var isMany = !this.$maxOccurs ? false : (isNaN(this.$maxOccurs) ? (this.$maxOccurs === 'unbounded') : (this.$maxOccurs > 1));
if (this.$minOccurs !== this.$maxOccurs && isMany) {
name += '[]';
}
var type = this.$type || this.$ref;
if (type) {
type = splitNSName(type);
var typeName = type.name,
ns = xmlns && xmlns[type.namespace] || definitions.xmlns[type.namespace],
schema = definitions.schemas[ns],
typeElement = schema && ( schema.complexTypes[typeName] || schema.types[typeName] || schema.elements[typeName] );
if (typeElement && !(typeName in Primitives)) {
if (!(typeName in definitions.descriptions.types)) {
var elem = {};
definitions.descriptions.types[typeName] = elem;
if (this.$ref) {
elem = element = typeElement.description(definitions, xmlns);
}
else {
elem = element[name] = typeElement.description(definitions, xmlns);
}
elem.targetNSAlias = type.namespace;
elem.targetNamespace = ns;
definitions.descriptions.types[typeName] = elem;
}
else {
if (this.$ref) {
element = definitions.descriptions.types[typeName];
}
else {
element[name] = definitions.descriptions.types[typeName];
}
}
}
else {
element[name] = this.$type;
}
}
else {
var children = this.children;
element[name] = {};
for (var i = 0, child; child = children[i]; i++) {
if (child instanceof ComplexTypeElement) {
element[name] = child.description(definitions, xmlns);
}
}
}
return element;
};
AllElement.prototype.description =
SequenceElement.prototype.description = function(definitions, xmlns) {
var children = this.children;
var sequence = {};
for (var i = 0, child; child = children[i]; i++) {
if (child instanceof AnyElement) {
continue;
}
var description = child.description(definitions, xmlns);
for (var key in description) {
sequence[key] = description[key];
}
}
return sequence;
};
ChoiceElement.prototype.description = function(definitions, xmlns) {
var children = this.children;
var choice = {};
for (var i=0, child; child=children[i]; i++) {
var description = child.description(definitions, xmlns);
for (var key in description) {
choice[key] = description[key];
}
}
return choice;
};
MessageElement.prototype.description = function(definitions) {
if (this.element) {
return this.element && this.element.description(definitions);
}
var desc = {};
desc[this.$name] = this.parts;
return desc;
};
PortTypeElement.prototype.description = function(definitions) {
var methods = {};
for (var name in this.methods) {
var method = this.methods[name];
methods[name] = method.description(definitions);
}
return methods;
};
OperationElement.prototype.description = function(definitions) {
var inputDesc = this.input ? this.input.description(definitions) : null;
var outputDesc = this.output ? this.output.description(definitions) : null;
return {
input: inputDesc && inputDesc[Object.keys(inputDesc)[0]],
output: outputDesc && outputDesc[Object.keys(outputDesc)[0]]
};
};
BindingElement.prototype.description = function(definitions) {
var methods = {};
for (var name in this.methods) {
var method = this.methods[name];
methods[name] = method.description(definitions);
}
return methods;
};
ServiceElement.prototype.description = function(definitions) {
var ports = {};
for (var name in this.ports) {
var port = this.ports[name];
ports[name] = port.binding.description(definitions);
}
return ports;
};
var WSDL = function(definition, uri, options) {
var self = this,
fromFunc;
this.uri = uri;
this.callback = function() {
};
this._initializeOptions(options);
if (typeof definition === 'string') {
definition = stripBom(definition);
fromFunc = this._fromXML;
}
else if (typeof definition === 'object') {
fromFunc = this._fromServices;
}
else {
throw new Error('WSDL constructor takes either an XML string or service definition');
}
process.nextTick(function() {
try {
fromFunc.call(self, definition);
} catch (e) {
return self.callback(e.message);
}
self.processIncludes(function(err) {
var name;
if (err) {
return self.callback(err);
}
self.definitions.deleteFixedAttrs();
var services = self.services = self.definitions.services;
if (services) {
for (name in services) {
services[name].postProcess(self.definitions);
}
}
var complexTypes = self.definitions.complexTypes;
if (complexTypes) {
for (name in complexTypes) {
complexTypes[name].deleteFixedAttrs();
}
}
// for document style, for every binding, prepare input message element name to (methodName, output message element name) mapping
var bindings = self.definitions.bindings;
for (var bindingName in bindings) {
var binding = bindings[bindingName];
if (typeof binding.style === 'undefined') {
binding.style = 'document';
}
if (binding.style !== 'document')
continue;
var methods = binding.methods;
var topEls = binding.topElements = {};
for (var methodName in methods) {
var inputName = methods[methodName].input.$name;
var outputName="";
if(methods[methodName].output )
outputName = methods[methodName].output.$name;
topEls[inputName] = {"methodName": methodName, "outputName": outputName};
}
}
// prepare soap envelope xmlns definition string
self.xmlnsInEnvelope = self._xmlnsMap();
self.callback(err, self);
});
});
};
WSDL.prototype.ignoredNamespaces = ['tns', 'targetNamespace', 'typedNamespace'];
WSDL.prototype.valueKey = '$value';
WSDL.prototype._initializeOptions = function (options) {
this.options = {};
var ignoredNamespaces = options ? options.ignoredNamespaces : null;
if (ignoredNamespaces &&
(Array.isArray(ignoredNamespaces.namespaces) || typeof ignoredNamespaces.namespaces === 'string')) {
if (ignoredNamespaces.override) {
this.options.ignoredNamespaces = ignoredNamespaces.namespaces;
} else {
this.options.ignoredNamespaces = this.ignoredNamespaces.concat(ignoredNamespaces.namespaces);
}
} else {
this.options.ignoredNamespaces = this.ignoredNamespaces;
}
this.options.valueKey = options.valueKey || this.valueKey;
};
WSDL.prototype.onReady = function(callback) {
if (callback)
this.callback = callback;
};
WSDL.prototype._processNextInclude = function(includes, callback) {
var self = this,
include = includes.shift();
if (!include)
return callback();
var includePath;
if (!/^http/.test(self.uri) && !/^http/.test(include.location)) {
includePath = path.resolve(path.dirname(self.uri), include.location);
} else {
includePath = url.resolve(self.uri, include.location);
}
open_wsdl(includePath, function(err, wsdl) {
if (err) {
return callback(err);
}
self.definitions.schemas[include.namespace || wsdl.definitions.$targetNamespace] = wsdl.definitions;
self._processNextInclude(includes, function(err) {
callback(err);
});
});
};
WSDL.prototype.processIncludes = function(callback) {
var schemas = this.definitions.schemas,
includes = [];
for (var ns in schemas) {
var schema = schemas[ns];
includes = includes.concat(schema.includes || []);
}
this._processNextInclude(includes, callback);
};
WSDL.prototype.describeServices = function() {
var services = {};
for (var name in this.services) {
var service = this.services[name];
services[name] = service.description(this.definitions);
}
return services;
};
WSDL.prototype.toXML = function() {
return this.xml || '';
};
WSDL.prototype.xmlToObject = function(xml) {
var self = this;
var p = sax.parser(true);
var objectName = null;
var root = {};
var schema = {
Envelope: {
Header: {
Security: {
UsernameToken: {
Username: 'string',
Password: 'string'
}
}
},
Body: {
Fault: {
faultcode: 'string',
faultstring: 'string',
detail: 'string'
}
}
}
};
var stack = [{name: null, object: root, schema: schema}];
var xmlns = {};
var refs = {}, id; // {id:{hrefs:[],obj:}, ...}
p.onopentag = function(node) {
var nsName = node.name;
var attrs = node.attributes;
var name = splitNSName(nsName).name,
attributeName,
top = stack[stack.length - 1],
topSchema = top.schema,
elementAttributes = {},
hasNonXmlnsAttribute = false,
hasNilAttribute = false,
obj = {};
var originalName = name;
if (!objectName && top.name === 'Body' && name !== 'Fault') {
var message = self.definitions.messages[name];
// Support RPC/literal messages where response body contains one element named
// after the operation + 'Response'. See http://www.w3.org/TR/wsdl#_names
if (!message) {
// Determine if this is request or response
var isInput = false;
var isOutput = false;
if ((/Response$/).test(name)) {
isOutput = true;
name = name.replace(/Response$/, '');
} else if ((/Request$/).test(name)) {
isInput = true;
name = name.replace(/Request$/, '');
} else if ((/Solicit$/).test(name)) {
isInput = true;
name = name.replace(/Solicit$/, '');
}
// Look up the appropriate message as given in the portType's operations
var portTypes = self.definitions.portTypes;
var portTypeNames = Object.keys(portTypes);
// Currently this supports only one portType definition.
var portType = portTypes[portTypeNames[0]];
if (isInput)
name = portType.methods[name].input.$name;
else
name = portType.methods[name].output.$name;
message = self.definitions.messages[name];
// 'cache' this alias to speed future lookups
self.definitions.messages[originalName] = self.definitions.messages[name];
}
topSchema = message.description(self.definitions);
objectName = originalName;
}
if (attrs.href) {
id = attrs.href.substr(1);
if (!refs[id])
refs[id] = {hrefs: [], obj: null};
refs[id].hrefs.push({par: top.object, key: name, obj: obj});
}
if (id = attrs.id) {
if (!refs[id])
refs[id] = {hrefs: [], obj: null};
}
//Handle element attributes
for(attributeName in attrs){
if(/^xmlns:|^xmlns$/.test(attributeName)){
xmlns[splitNSName(attributeName).name] = attrs[attributeName];
continue;
}
hasNonXmlnsAttribute = true;
elementAttributes[attributeName] = attrs[attributeName];
}
for(attributeName in elementAttributes){
var res = splitNSName(attributeName);
if(res.name === 'nil' && xmlns[res.namespace] === 'http://www.w3.org/2001/XMLSchema-instance'){
hasNilAttribute = true;
break;
}
}
if(hasNonXmlnsAttribute)obj[self.options.attributesKey] = elementAttributes;
if (topSchema && topSchema[name + '[]'])
name = name + '[]';
stack.push({name: originalName, object: obj, schema: topSchema && topSchema[name], id: attrs.id, nil: hasNilAttribute});
};
p.onclosetag = function(nsName) {
var cur = stack.pop(),
obj = cur.object,
top = stack[stack.length - 1],
topObject = top.object,
topSchema = top.schema,
name = splitNSName(nsName).name;
if(cur.nil === true) {
return;
}
if (topSchema && topSchema[name + '[]']) {
if (!topObject[name])
topObject[name] = [];
topObject[name].push(obj);
}
else if (name in topObject) {
if (!Array.isArray(topObject[name])) {
topObject[name] = [topObject[name]];
}
topObject[name].push(obj);
}
else {
topObject[name] = obj;
}
if (cur.id) {
refs[cur.id].obj = obj;
}
};
p.ontext = function(text) {
text = trim(text);
if (!text.length)
return;
var top = stack[stack.length - 1];
var name = splitNSName(top.schema).name,
value;
if (name === 'int' || name === 'integer') {
value = parseInt(text, 10);
} else if (name === 'bool' || name === 'boolean') {
value = text.toLowerCase() === 'true' || text === '1';
} else if (name === 'dateTime' || name === 'date') {
value = new Date(text);
} else {
// handle string or other types
if (typeof top.object !== 'string') {
value = text;
} else {
value = top.object + text;
}
}
if(top.object[self.options.attributesKey]) {
top.object[self.options.valueKey] = value;
} else {
top.object = value;
}
};
p.write(xml).close();
// merge obj with href
var merge = function(href, obj) {
for (var j in obj) {
if (obj.hasOwnProperty(j)) {
href.obj[j] = obj[j];
}
}
};
// MultiRef support: merge objects instead of replacing
for (var n in refs) {
var ref = refs[n];
for (var i = 0; i < ref.hrefs.length; i++) {
merge(ref.hrefs[i], ref.obj);
}
}
var body = root.Envelope.Body;
if (body.Fault) {
var error = new Error(body.Fault.faultcode + ': ' + body.Fault.faultstring + (body.Fault.detail ? ': ' + body.Fault.detail : ''));
error.root = root;
throw error;
}
return root.Envelope;
};
WSDL.prototype.findParameterObject = function(xmlns, parameterType) {
if (!xmlns || !parameterType) {
return null;
}
var parameterTypeObj = null;
if (this.definitions.schemas) {
var schema = this.definitions.schemas[xmlns];
if (schema) {
if (parameterType.indexOf(':') !== -1) {
parameterType = parameterType.substring(parameterType.indexOf(':') + 1, parameterType.length);
}
// if the client passed an input element which has a `$lookupType` property instead of `$type`
// the `parameterTypeObj` is found in `schema.elements`.
parameterTypeObj = schema.complexTypes[parameterType] || schema.elements[parameterType];
}
}
return parameterTypeObj;
};
WSDL.prototype.objectToDocumentXML = function(name, params, ns, xmlns, type) {
var args = {};
args[name] = params;
var parameterTypeObj = type ? this.findParameterObject(xmlns, type) : null;
if (this.namespaceNumber) {
this.namespaceNumber = 0;
}
return this.objectToXML(args, null, ns, xmlns, true, null, parameterTypeObj);
};
WSDL.prototype.objectToRpcXML = function(name, params, namespace, xmlns) {
var self = this;
var parts = [];
var defs = this.definitions;
var nsAttrName = '_xmlns';
namespace = namespace || findKey(defs.xmlns, xmlns);
xmlns = xmlns || defs.xmlns[namespace];
namespace = namespace === 'xmlns' ? '' : (namespace + ':');
parts.push(['<', namespace, name, '>'].join(''));
for (var key in params) {
if (key !== nsAttrName) {
var value = params[key];
parts.push(['<', key, '>'].join(''));
parts.push((typeof value === 'object') ? this.objectToXML(value, key, namespace, xmlns) : xmlEscape(value));
parts.push(['</', key, '>'].join(''));
}
}
parts.push(['</', namespace, name, '>'].join(''));
return parts.join('');
};
WSDL.prototype.objectToXML = function(obj, name, namespace, xmlns, first, xmlnsAttr, parameterTypeObject, ancestorXmlns) {
var self = this;
var schema = this.definitions.schemas[xmlns];
var qualified = schema && schema.$elementFormDefault === 'qualified';
var parts = [];
var xmlnsAttrib = '';
if (xmlns && first) {
if (namespace && namespace !== 'xmlns') {
xmlnsAttrib += ' xmlns:' + namespace + '="' + xmlns + '"';
}
xmlnsAttrib += ' xmlns="' + xmlns + '"';
}
var ancXmlns = first ? new Array(xmlns) : ancestorXmlns;
// explicitly use xmlns attribute if available
if (xmlnsAttr) {
xmlnsAttrib = xmlnsAttr;
}
var ns = '';
if (namespace && namespace !== 'xmlns' && qualified) {
ns = namespace.indexOf(":") === -1 ? namespace + ':' : namespace;
}
if (Array.isArray(obj)) {
for (var i = 0, item; item = obj[i]; i++) {
var arrayAttr = self.processAttributes(item);
parts.push(['<', ns, name, arrayAttr, xmlnsAttrib, '>'].join(''));
parts.push(self.objectToXML(item, name, namespace, xmlns, false, null, null, ancXmlns));
parts.push(['</', ns, name, '>'].join(''));
}
} else if (typeof obj === 'object') {
for (name in obj) {
//don't process attributes as element
if (name === self.options.attributesKey) {
continue;
}
//Its the value of an item. Return it directly.
if (name === self.options.valueKey) {
return xmlEscape(obj[name]);
}
var child = obj[name];
if (typeof child === 'undefined')
continue;
var attr = self.processAttributes(child);
var value = '';
if (first) {
value = self.objectToXML(child, name, namespace, xmlns, false, null, parameterTypeObject, ancXmlns);
} else {
if (self.definitions.schemas) {
if (schema) {
var childParameterTypeObject = self.findChildParameterObject(parameterTypeObject, name);
//find sub namespace if not a primitive
if (childParameterTypeObject && childParameterTypeObject.$type && (childParameterTypeObject.$type.indexOf('xsd') === -1)) {
var childParameterType = childParameterTypeObject.$type;
var childNamespace = '';
var childName = '';
if (childParameterType.indexOf(':') !== -1) {
childNamespace = childParameterType.substring(0, childParameterType.indexOf(':'));
childName = childParameterType.substring(childParameterType.indexOf(':') + 1);
}
var childXmlns = schema.xmlns[childNamespace] || self.definitions.xmlns[childNamespace];
var childXmlnsAttrib = '';
if ((ancXmlns.indexOf(childXmlns) === -1) && (childXmlns)) {
childXmlnsAttrib = ' xmlns:' + childNamespace + '="' + childXmlns+ '"';
if ((childXmlns)) {
ancXmlns.push(childXmlns);
}
}
var completeChildParameterTypeObject = self.findChildParameterObjectFromSchema(childName, childXmlns) || childParameterTypeObject;
value = self.objectToXML(child, name, childNamespace, childXmlns, false, childXmlnsAttrib, completeChildParameterTypeObject, ancXmlns);
} else if (obj[self.options.attributesKey] && obj[self.options.attributesKey].xsi_type) { //if parent object has complex type defined and child not found in parent
var completeChildParamTypeObject = self.findChildParameterObjectFromSchema(obj[self.options.attributesKey].xsi_type.type, obj[self.options.attributesKey].xsi_type.xmlns);
ns = obj[self.options.attributesKey].xsi_type.namespace + ':';
ancXmlns.push(obj[self.options.attributesKey].xsi_type.xmlns);
value = self.objectToXML(child, name, obj[self.options.attributesKey].xsi_type.namespace, obj[self.options.attributesKey].xsi_type.xmlns, false, null, null, ancXmlns);
} else {
value = self.objectToXML(child, name, namespace, xmlns, false, null, null, ancXmlns);
}
} else {
value = self.objectToXML(child, name, namespace, xmlns, false, null, null, ancXmlns);
}
}
}
if (!Array.isArray(child)) {
parts.push(['<', ns, name, attr, xmlnsAttrib, '>'].join(''));
}
parts.push(value);
if (!Array.isArray(child)) {
parts.push(['</', ns, name, '>'].join(''));
}
}
} else if (obj !== undefined) {
parts.push(xmlEscape(obj));
}
return parts.join('');
};
WSDL.prototype.processAttributes = function(child) {
var self = this;
var attr = '';
if (child[this.options.attributesKey] && child[this.options.attributesKey].xsi_type) {
var xsiType = child[this.options.attributesKey].xsi_type;
// Generate a new namespace for complex extension if one not provided
if (!xsiType.namespace) {
if (self.namespaceNumber) {
self.namespaceNumber++;
} else {
self.namespaceNumber = 1;
}
xsiType.namespace = 'ns' + self.namespaceNumber;
}
}
if (child[this.options.attributesKey]) {
for (var attrKey in child[this.options.attributesKey]) {
//handle complex extension separately
if (attrKey === 'xsi_type') {
var attrValue = child[this.options.attributesKey][attrKey];
attr += ' xsi:type="' + attrValue.namespace + ':' + attrValue.type + '"';
attr += ' xmlns:' + attrValue.namespace + '="' + attrValue.xmlns + '"';
continue;
} else {
attr += ' ' + attrKey + '="' + xmlEscape(child[this.options.attributesKey][attrKey]) + '"';
}
}
}
return attr;
};
WSDL.prototype.findChildParameterObjectFromSchema = function(name, xmlns) {
if (!this.definitions.schemas || !name || !xmlns) {
return null;
}
var schema = this.definitions.schemas[xmlns];
if (!schema || !schema.complexTypes) {
return null;
}
return schema.complexTypes[name];
};
WSDL.prototype.findChildParameterObject = function(parameterTypeObj, childName) {
if (!parameterTypeObj || !childName) {
return null;
}
var found = null,
i = 0,
child;
if(parameterTypeObj.$lookupTypes && Array.isArray(parameterTypeObj.$lookupTypes)) {
var types = parameterTypeObj.$lookupTypes;
for(i = 0; i < types.length; i++) {
var typeObj = types[i];
if(typeObj.$name === childName) {
found = typeObj;
break;
}
}
} else {
var object = parameterTypeObj;
if (object.$name === childName) {
return object;
}
if (object.children) {
for (i = 0, child; child = object.children[i]; i++) {
found = this.findChildParameterObject(child, childName);
if (found) {
break;
}
}
}
}
return found;
};
WSDL.prototype._parse = function(xml) {
var self = this,
p = sax.parser(true),
stack = [],
root = null,
options = self.options;
p.onopentag = function(node) {
var nsName = node.name;
var attrs = node.attributes;
var top = stack[stack.length - 1];
var name;
if (top) {
try {
top.startElement(stack, nsName, attrs, options);
} catch (e) {
if (self.options.strict) {
throw e;
} else {
stack.push(new Element(nsName, attrs, options));
}
}
} else {
name = splitNSName(nsName).name;
if (name === 'definitions') {
root = new DefinitionsElement(nsName, attrs, options);
} else if (name === 'schema') {
root = new SchemaElement(nsName, attrs, options);
} else {
throw new Error('Unexpected root element of WSDL or include');
}
stack.push(root);
}
};
p.onclosetag = function(name) {
var top = stack[stack.length - 1];
assert(top, 'Unmatched close tag: ' + name);
top.endElement(stack, name);
};
p.write(xml).close();
return root;
};
WSDL.prototype._fromXML = function(xml) {
this.definitions = this._parse(xml);
this.definitions.descriptions = {
types:{}
};
this.xml = xml;
};
WSDL.prototype._fromServices = function(services) {
};
WSDL.prototype._xmlnsMap = function() {
var xmlns = this.definitions.xmlns;
var str = '';
for (var alias in xmlns) {
if (alias === '' || alias === 'xmlns')
continue;
var ns = xmlns[alias];
switch (ns) {
case "http://xml.apache.org/xml-soap" : // apachesoap
case "http://schemas.xmlsoap.org/wsdl/" : // wsdl
case "http://schemas.xmlsoap.org/wsdl/soap/" : // wsdlsoap
case "http://schemas.xmlsoap.org/soap/encoding/" : // soapenc
case "http://www.w3.org/2001/XMLSchema" : // xsd
continue;
}
if (~ns.indexOf('http://schemas.xmlsoap.org/'))
continue;
if (~ns.indexOf('http://www.w3.org/'))
continue;
if (~ns.indexOf('http://xml.apache.org/'))
continue;
str += ' xmlns:' + alias + '="' + ns + '"';
}
return str;
};
function open_wsdl(uri, options, callback) {
if (typeof options === 'function') {
callback = options;
options = {};
}
var request_headers = options.wsdl_headers;
var request_options = options.wsdl_options;
delete options.wsdl_headers;
delete options.wsdl_options;
var wsdl;
if (!/^http/.test(uri)) {
fs.readFile(uri, 'utf8', function(err, definition) {
if (err) {
callback(err);
}
else {
wsdl = new WSDL(definition, uri, options);
wsdl.onReady(callback);
}
});
}
else {
http.request(uri, null /* options */, function(err, response, definition) {
if (err) {
callback(err);
} else if (response && response.statusCode === 200) {
wsdl = new WSDL(definition, uri, options);
wsdl.onReady(callback);
} else {
callback(new Error('Invalid WSDL URL: ' + uri + "\n\n\r Code: " + response.statusCode + "\n\n\r Response Body: " + response.body));
}
}, request_headers, request_options);
}
return wsdl;
}
exports.open_wsdl = open_wsdl;
exports.WSDL = WSDL;