docs: function (options) {
          var self = this,
              opts = options || {},
              attrTag = opts.attrTag,
              textTag = opts.textTag || '#text',
              skipWS = opts.skipWSText;
          if (attrTag === undefined) {
              attrTag = '@';
          }
          self.rootNode = {};
          self.stack = [];
          self.nsStack = [];
          self.parser = sax.parser(true);
          // TODO make this configurable
          self.nsMap = {
              'http://www.w3.org/2001/XMLSchema-instance': 'xsi',
              'http://www.w3.org/2001/XMLSchema': 'xsd'
          };
  
          self.parser.ontext = function (text) {
              if (self.stack.length > 0) {
                  if (skipWS) {
                      if (text.replace(/\s+?/g, '')) {
                          self.stack[self.stack.length - 1][textTag] = text;
                      }
                  } else {
                      self.stack[self.stack.length - 1][textTag] = text;
                  }
              }
          };
  
          function mapNamespace(ns, value) {
              var colon = value.indexOf(':');
              if (colon === -1) {
                  return value;
              }
              var namespace = value.substr(0, colon);
              if (namespace in ns) {
                  return (self.nsMap[ns[namespace]] || ns[namespace]) + ':' + value.substr(colon + 1);
              }
              return value;
          }
          self.parser.onopentag = function (node) {
              var key,
                  i,
                  parentNode,
                  nodeName,
                  jsonNode = {};
  
              var ns = {};
              for (key in node.attributes) {
                  if (node.attributes.hasOwnProperty(key)) {
                      if (key.substr(0, 6) === 'xmlns:') {
                          ns[key.substr('xmlns:'.length)] = node.attributes[key];
                      }
                      if (key === 'xmlns') {
                          ns[''] = node.attributes.xmlns;
                      }
                  }
              }
              if (Object.getOwnPropertyNames(ns).length === 0) {
                  if (self.nsStack.length > 0) {
                      ns = self.nsStack[self.nsStack.length - 1];
                  }
                  self.nsStack.push(ns);
              } else {
                  for (i = self.nsStack.length - 1; i >= 0; i--) {
                      for (key in self.nsStack[i]) {
                          if (!ns.hasOwnProperty(key) && self.nsStack[i].hasOwnProperty(key)) {
                              ns[key] = self.nsStack[i][key];
                          }
                      }
                  }
                  self.nsStack.push(ns);
              }
              nodeName = mapNamespace(ns, node.name);
              if (self.stack.length === 0) {
                  self.rootNode[nodeName] = jsonNode;
              } else {
                  parentNode = self.stack[self.stack.length - 1];
                  if (opts.arrayElements) {
                      self.arrayElements = opts.arrayElements;
                      if (self.arrayElements[nodeName]) {
                          if (parentNode.hasOwnProperty(nodeName)) {
                              parentNode[nodeName].push(jsonNode);
                          } else {
                              parentNode[nodeName] = [jsonNode];
                          }
                      } else {
                          parentNode[nodeName] = jsonNode;
                      }
                  } else {
                      if (parentNode.hasOwnProperty(nodeName)) {
                          if (parentNode[nodeName] instanceof Array) {
                              parentNode[nodeName].push(jsonNode);
                          } else {
                              parentNode[nodeName] = [parentNode[nodeName], jsonNode];
                          }
                      } else {
                          parentNode[nodeName] = jsonNode;
                      }
                  }
              }
              self.stack.push(jsonNode);
              for (key in node.attributes) {
                  if (node.attributes.hasOwnProperty(key)) {
                      var namespaceKey = mapNamespace(ns, key);
                      if (namespaceKey === 'xsi:type') {
                          // the attribute value should be mapped too
                          jsonNode[attrTag + namespaceKey] = mapNamespace(ns, node.attributes[key]);
                      } else {
                          jsonNode[attrTag + namespaceKey] = node.attributes[key];
                      }
                  }
              }
          };
  
          self.parser.onclosetag = function (/*node*/) {
              self.stack.pop();
              self.nsStack.pop();
          };
  
          self.parser.onerror = function (error) {
              self.rootNode = error;
              self.parser.error = null;
          };
      }