browser_parser.js
  1  var util = require('../util');
  2  var Shape = require('../model/shape');
  3  
  4  function DomXmlParser() { }
  5  
  6  DomXmlParser.prototype.parse = function(xml, shape) {
  7    if (xml.replace(/^\s+/, '') === '') return {};
  8  
  9    var result, error;
 10    try {
 11      if (window.DOMParser) {
 12        try {
 13          var parser = new DOMParser();
 14          result = parser.parseFromString(xml, 'text/xml');
 15        } catch (syntaxError) {
 16          throw util.error(new Error('Parse error in document'),
 17            {
 18              originalError: syntaxError,
 19              code: 'XMLParserError',
 20              retryable: true
 21            });
 22        }
 23  
 24        if (result.documentElement === null) {
 25          throw util.error(new Error('Cannot parse empty document.'),
 26            {
 27              code: 'XMLParserError',
 28              retryable: true
 29            });
 30        }
 31  
 32        var isError = result.getElementsByTagName('parsererror')[0];
 33        if (isError && (isError.parentNode === result ||
 34            isError.parentNode.nodeName === 'body' ||
 35            isError.parentNode.parentNode === result ||
 36            isError.parentNode.parentNode.nodeName === 'body')) {
 37          var errorElement = isError.getElementsByTagName('div')[0] || isError;
 38          throw util.error(new Error(errorElement.textContent || 'Parser error in document'),
 39            {
 40              code: 'XMLParserError',
 41              retryable: true
 42            });
 43        }
 44      } else if (window.ActiveXObject) {
 45        result = new window.ActiveXObject('Microsoft.XMLDOM');
 46        result.async = false;
 47  
 48        if (!result.loadXML(xml)) {
 49          throw util.error(new Error('Parse error in document'),
 50            {
 51              code: 'XMLParserError',
 52              retryable: true
 53            });
 54        }
 55      } else {
 56        throw new Error('Cannot load XML parser');
 57      }
 58    } catch (e) {
 59      error = e;
 60    }
 61  
 62    if (result && result.documentElement && !error) {
 63      var data = parseXml(result.documentElement, shape);
 64      var metadata = getElementByTagName(result.documentElement, 'ResponseMetadata');
 65      if (metadata) {
 66        data.ResponseMetadata = parseXml(metadata, {});
 67      }
 68      return data;
 69    } else if (error) {
 70      throw util.error(error || new Error(), {code: 'XMLParserError', retryable: true});
 71    } else { // empty xml document
 72      return {};
 73    }
 74  };
 75  
 76  function getElementByTagName(xml, tag) {
 77    var elements = xml.getElementsByTagName(tag);
 78    for (var i = 0, iLen = elements.length; i < iLen; i++) {
 79      if (elements[i].parentNode === xml) {
 80        return elements[i];
 81      }
 82    }
 83  }
 84  
 85  function parseXml(xml, shape) {
 86    if (!shape) shape = {};
 87    switch (shape.type) {
 88      case 'structure': return parseStructure(xml, shape);
 89      case 'map': return parseMap(xml, shape);
 90      case 'list': return parseList(xml, shape);
 91      case undefined: case null: return parseUnknown(xml);
 92      default: return parseScalar(xml, shape);
 93    }
 94  }
 95  
 96  function parseStructure(xml, shape) {
 97    var data = {};
 98    if (xml === null) return data;
 99  
100    util.each(shape.members, function(memberName, memberShape) {
101      if (memberShape.isXmlAttribute) {
102        if (Object.prototype.hasOwnProperty.call(xml.attributes, memberShape.name)) {
103          var value = xml.attributes[memberShape.name].value;
104          data[memberName] = parseXml({textContent: value}, memberShape);
105        }
106      } else {
107        var xmlChild = memberShape.flattened ? xml :
108          getElementByTagName(xml, memberShape.name);
109        if (xmlChild) {
110          data[memberName] = parseXml(xmlChild, memberShape);
111        } else if (
112          !memberShape.flattened &&
113          memberShape.type === 'list' &&
114          !shape.api.xmlNoDefaultLists) {
115          data[memberName] = memberShape.defaultValue;
116        }
117      }
118    });
119  
120    return data;
121  }
122  
123  function parseMap(xml, shape) {
124    var data = {};
125    var xmlKey = shape.key.name || 'key';
126    var xmlValue = shape.value.name || 'value';
127    var tagName = shape.flattened ? shape.name : 'entry';
128  
129    var child = xml.firstElementChild;
130    while (child) {
131      if (child.nodeName === tagName) {
132        var key = getElementByTagName(child, xmlKey).textContent;
133        var value = getElementByTagName(child, xmlValue);
134        data[key] = parseXml(value, shape.value);
135      }
136      child = child.nextElementSibling;
137    }
138    return data;
139  }
140  
141  function parseList(xml, shape) {
142    var data = [];
143    var tagName = shape.flattened ? shape.name : (shape.member.name || 'member');
144  
145    var child = xml.firstElementChild;
146    while (child) {
147      if (child.nodeName === tagName) {
148        data.push(parseXml(child, shape.member));
149      }
150      child = child.nextElementSibling;
151    }
152    return data;
153  }
154  
155  function parseScalar(xml, shape) {
156    if (xml.getAttribute) {
157      var encoding = xml.getAttribute('encoding');
158      if (encoding === 'base64') {
159        shape = new Shape.create({type: encoding});
160      }
161    }
162  
163    var text = xml.textContent;
164    if (text === '') text = null;
165    if (typeof shape.toType === 'function') {
166      return shape.toType(text);
167    } else {
168      return text;
169    }
170  }
171  
172  function parseUnknown(xml) {
173    if (xml === undefined || xml === null) return '';
174  
175    // empty object
176    if (!xml.firstElementChild) {
177      if (xml.parentNode.parentNode === null) return {};
178      if (xml.childNodes.length === 0) return '';
179      else return xml.textContent;
180    }
181  
182    // object, parse as structure
183    var shape = {type: 'structure', members: {}};
184    var child = xml.firstElementChild;
185    while (child) {
186      var tag = child.nodeName;
187      if (Object.prototype.hasOwnProperty.call(shape.members, tag)) {
188        // multiple tags of the same name makes it a list
189        shape.members[tag].type = 'list';
190      } else {
191        shape.members[tag] = {name: tag};
192      }
193      child = child.nextElementSibling;
194    }
195    return parseStructure(xml, shape);
196  }
197  
198  /**
199   * @api private
200   */
201  module.exports = DomXmlParser;