node_parser.js
  1  var AWS = require('../core');
  2  var util = AWS.util;
  3  var Shape = AWS.Model.Shape;
  4  
  5  var xml2js = require('xml2js');
  6  
  7  /**
  8   * @api private
  9   */
 10  var options = {  // options passed to xml2js parser
 11    explicitCharkey: false, // undocumented
 12    trim: false,            // trim the leading/trailing whitespace from text nodes
 13    normalize: false,       // trim interior whitespace inside text nodes
 14    explicitRoot: false,    // return the root node in the resulting object?
 15    emptyTag: null,         // the default value for empty nodes
 16    explicitArray: true,    // always put child nodes in an array
 17    ignoreAttrs: false,     // ignore attributes, only create text nodes
 18    mergeAttrs: false,      // merge attributes and child elements
 19    validator: null         // a callable validator
 20  };
 21  
 22  function NodeXmlParser() { }
 23  
 24  NodeXmlParser.prototype.parse = function(xml, shape) {
 25    shape = shape || {};
 26  
 27    var result = null;
 28    var error = null;
 29  
 30    var parser = new xml2js.Parser(options);
 31    parser.parseString(xml, function (e, r) {
 32      error = e;
 33      result = r;
 34    });
 35  
 36    if (result) {
 37      var data = parseXml(result, shape);
 38      if (result.ResponseMetadata) {
 39        data.ResponseMetadata = parseXml(result.ResponseMetadata[0], {});
 40      }
 41      return data;
 42    } else if (error) {
 43      throw util.error(error, {code: 'XMLParserError', retryable: true});
 44    } else { // empty xml document
 45      return parseXml({}, shape);
 46    }
 47  };
 48  
 49  function parseXml(xml, shape) {
 50    switch (shape.type) {
 51      case 'structure': return parseStructure(xml, shape);
 52      case 'map': return parseMap(xml, shape);
 53      case 'list': return parseList(xml, shape);
 54      case undefined: case null: return parseUnknown(xml);
 55      default: return parseScalar(xml, shape);
 56    }
 57  }
 58  
 59  function parseStructure(xml, shape) {
 60    var data = {};
 61    if (xml === null) return data;
 62  
 63    util.each(shape.members, function(memberName, memberShape) {
 64      var xmlName = memberShape.name;
 65      if (Object.prototype.hasOwnProperty.call(xml, xmlName) && Array.isArray(xml[xmlName])) {
 66        var xmlChild = xml[xmlName];
 67        if (!memberShape.flattened) xmlChild = xmlChild[0];
 68  
 69        data[memberName] = parseXml(xmlChild, memberShape);
 70      } else if (memberShape.isXmlAttribute &&
 71                 xml.$ && Object.prototype.hasOwnProperty.call(xml.$, xmlName)) {
 72        data[memberName] = parseScalar(xml.$[xmlName], memberShape);
 73      } else if (memberShape.type === 'list' && !shape.api.xmlNoDefaultLists) {
 74        data[memberName] = memberShape.defaultValue;
 75      }
 76    });
 77  
 78    return data;
 79  }
 80  
 81  function parseMap(xml, shape) {
 82    var data = {};
 83    if (xml === null) return data;
 84  
 85    var xmlKey = shape.key.name || 'key';
 86    var xmlValue = shape.value.name || 'value';
 87    var iterable = shape.flattened ? xml : xml.entry;
 88  
 89    if (Array.isArray(iterable)) {
 90      util.arrayEach(iterable, function(child) {
 91        data[child[xmlKey][0]] = parseXml(child[xmlValue][0], shape.value);
 92      });
 93    }
 94  
 95    return data;
 96  }
 97  
 98  function parseList(xml, shape) {
 99    var data = [];
100    var name = shape.member.name || 'member';
101    if (shape.flattened) {
102      util.arrayEach(xml, function(xmlChild) {
103        data.push(parseXml(xmlChild, shape.member));
104      });
105    } else if (xml && Array.isArray(xml[name])) {
106      util.arrayEach(xml[name], function(child) {
107        data.push(parseXml(child, shape.member));
108      });
109    }
110  
111    return data;
112  }
113  
114  function parseScalar(text, shape) {
115    if (text && text.$ && text.$.encoding === 'base64') {
116      shape = new Shape.create({type: text.$.encoding});
117    }
118    if (text && text._) text = text._;
119  
120    if (typeof shape.toType === 'function') {
121      return shape.toType(text);
122    } else {
123      return text;
124    }
125  }
126  
127  function parseUnknown(xml) {
128    if (xml === undefined || xml === null) return '';
129    if (typeof xml === 'string') return xml;
130  
131    // parse a list
132    if (Array.isArray(xml)) {
133      var arr = [];
134      for (i = 0; i < xml.length; i++) {
135        arr.push(parseXml(xml[i], {}));
136      }
137      return arr;
138    }
139  
140    // empty object
141    var keys = Object.keys(xml), i;
142    if (keys.length === 0 || (keys.length === 1 && keys[0] === '$')) {
143      return {};
144    }
145  
146    // object, parse as structure
147    var data = {};
148    for (i = 0; i < keys.length; i++) {
149      var key = keys[i], value = xml[key];
150      if (key === '$') continue;
151      if (value.length > 1) { // this member is a list
152        data[key] = parseList(value, {member: {}});
153      } else { // this member is a single item
154        data[key] = parseXml(value[0], {});
155      }
156    }
157    return data;
158  }
159  
160  /**
161   * @api private
162   */
163  module.exports = NodeXmlParser;